]> git.andy128k.dev Git - ipf-legacy-orm.git/commitdiff
move to src folder
authorAndrey Kutejko <andy128k@gmail.com>
Sun, 7 Sep 2014 21:36:36 +0000 (00:36 +0300)
committerAndrey Kutejko <andy128k@gmail.com>
Sun, 7 Sep 2014 21:36:36 +0000 (00:36 +0300)
240 files changed:
ipf/legacy_orm/adminmodel.php [deleted file]
ipf/legacy_orm/adminmodelinline.php [deleted file]
ipf/legacy_orm/app.php [deleted file]
ipf/legacy_orm/commands/buildmodels.php [deleted file]
ipf/legacy_orm/commands/fixtures.php [deleted file]
ipf/legacy_orm/commands/sql.php [deleted file]
ipf/legacy_orm/commands/syncdb.php [deleted file]
ipf/legacy_orm/modelform.php [deleted file]
ipf/legacy_orm/orm.php [deleted file]
ipf/legacy_orm/orm/access.php [deleted file]
ipf/legacy_orm/orm/adapter.php [deleted file]
ipf/legacy_orm/orm/cache/interface.php [deleted file]
ipf/legacy_orm/orm/collection.php [deleted file]
ipf/legacy_orm/orm/configurable.php [deleted file]
ipf/legacy_orm/orm/connection.php [deleted file]
ipf/legacy_orm/orm/connection/module.php [deleted file]
ipf/legacy_orm/orm/connection/mysql.php [deleted file]
ipf/legacy_orm/orm/connection/statement.php [deleted file]
ipf/legacy_orm/orm/connection/unitofwork.php [deleted file]
ipf/legacy_orm/orm/event.php [deleted file]
ipf/legacy_orm/orm/exception.php [deleted file]
ipf/legacy_orm/orm/exception/adapter.php [deleted file]
ipf/legacy_orm/orm/exception/connection.php [deleted file]
ipf/legacy_orm/orm/exception/locator.php [deleted file]
ipf/legacy_orm/orm/exception/mysql.php [deleted file]
ipf/legacy_orm/orm/exception/profiler.php [deleted file]
ipf/legacy_orm/orm/exception/validator.php [deleted file]
ipf/legacy_orm/orm/export.php [deleted file]
ipf/legacy_orm/orm/export/mysql.php [deleted file]
ipf/legacy_orm/orm/expression.php [deleted file]
ipf/legacy_orm/orm/expression/driver.php [deleted file]
ipf/legacy_orm/orm/expression/mysql.php [deleted file]
ipf/legacy_orm/orm/hydrator.php [deleted file]
ipf/legacy_orm/orm/hydrator/abstract.php [deleted file]
ipf/legacy_orm/orm/hydrator/arraydriver.php [deleted file]
ipf/legacy_orm/orm/hydrator/recorddriver.php [deleted file]
ipf/legacy_orm/orm/import/builder.php [deleted file]
ipf/legacy_orm/orm/import/schema.php [deleted file]
ipf/legacy_orm/orm/inflector.php [deleted file]
ipf/legacy_orm/orm/manager.php [deleted file]
ipf/legacy_orm/orm/null.php [deleted file]
ipf/legacy_orm/orm/pager.php [deleted file]
ipf/legacy_orm/orm/pager/layout.php [deleted file]
ipf/legacy_orm/orm/pager/layoutarrows.php [deleted file]
ipf/legacy_orm/orm/pager/range.php [deleted file]
ipf/legacy_orm/orm/pager/range/jumping.php [deleted file]
ipf/legacy_orm/orm/pager/range/sliding.php [deleted file]
ipf/legacy_orm/orm/query.php [deleted file]
ipf/legacy_orm/orm/query/abstract.php [deleted file]
ipf/legacy_orm/orm/query/check.php [deleted file]
ipf/legacy_orm/orm/query/condition.php [deleted file]
ipf/legacy_orm/orm/query/filter.php [deleted file]
ipf/legacy_orm/orm/query/filter/chain.php [deleted file]
ipf/legacy_orm/orm/query/filter/interface.php [deleted file]
ipf/legacy_orm/orm/query/from.php [deleted file]
ipf/legacy_orm/orm/query/groupby.php [deleted file]
ipf/legacy_orm/orm/query/having.php [deleted file]
ipf/legacy_orm/orm/query/joincondition.php [deleted file]
ipf/legacy_orm/orm/query/limit.php [deleted file]
ipf/legacy_orm/orm/query/offset.php [deleted file]
ipf/legacy_orm/orm/query/orderby.php [deleted file]
ipf/legacy_orm/orm/query/parser.php [deleted file]
ipf/legacy_orm/orm/query/part.php [deleted file]
ipf/legacy_orm/orm/query/registry.php [deleted file]
ipf/legacy_orm/orm/query/select.php [deleted file]
ipf/legacy_orm/orm/query/set.php [deleted file]
ipf/legacy_orm/orm/query/tokenizer.php [deleted file]
ipf/legacy_orm/orm/query/where.php [deleted file]
ipf/legacy_orm/orm/rawsql.php [deleted file]
ipf/legacy_orm/orm/record.php [deleted file]
ipf/legacy_orm/orm/record/abstract.php [deleted file]
ipf/legacy_orm/orm/record/iterator.php [deleted file]
ipf/legacy_orm/orm/relation.php [deleted file]
ipf/legacy_orm/orm/relation/association.php [deleted file]
ipf/legacy_orm/orm/relation/foreignkey.php [deleted file]
ipf/legacy_orm/orm/relation/localkey.php [deleted file]
ipf/legacy_orm/orm/relation/nest.php [deleted file]
ipf/legacy_orm/orm/relation/parser.php [deleted file]
ipf/legacy_orm/orm/table.php [deleted file]
ipf/legacy_orm/orm/table/repository.php [deleted file]
ipf/legacy_orm/orm/template.php [deleted file]
ipf/legacy_orm/orm/template/listener/orderable.php [deleted file]
ipf/legacy_orm/orm/template/listener/owned.php [deleted file]
ipf/legacy_orm/orm/template/listener/sluggable.php [deleted file]
ipf/legacy_orm/orm/template/listener/timestampable.php [deleted file]
ipf/legacy_orm/orm/template/orderable.php [deleted file]
ipf/legacy_orm/orm/template/owned.php [deleted file]
ipf/legacy_orm/orm/template/sluggable.php [deleted file]
ipf/legacy_orm/orm/template/timestampable.php [deleted file]
ipf/legacy_orm/orm/transaction.php [deleted file]
ipf/legacy_orm/orm/transaction/mysql.php [deleted file]
ipf/legacy_orm/orm/utils.php [deleted file]
ipf/legacy_orm/orm/validator.php [deleted file]
ipf/legacy_orm/orm/validator/country.php [deleted file]
ipf/legacy_orm/orm/validator/creditcard.php [deleted file]
ipf/legacy_orm/orm/validator/date.php [deleted file]
ipf/legacy_orm/orm/validator/driver.php [deleted file]
ipf/legacy_orm/orm/validator/email.php [deleted file]
ipf/legacy_orm/orm/validator/exclude.php [deleted file]
ipf/legacy_orm/orm/validator/file.php [deleted file]
ipf/legacy_orm/orm/validator/future.php [deleted file]
ipf/legacy_orm/orm/validator/html.php [deleted file]
ipf/legacy_orm/orm/validator/htmlcolor.php [deleted file]
ipf/legacy_orm/orm/validator/image.php [deleted file]
ipf/legacy_orm/orm/validator/ip.php [deleted file]
ipf/legacy_orm/orm/validator/minlength.php [deleted file]
ipf/legacy_orm/orm/validator/nospace.php [deleted file]
ipf/legacy_orm/orm/validator/notblank.php [deleted file]
ipf/legacy_orm/orm/validator/notnull.php [deleted file]
ipf/legacy_orm/orm/validator/past.php [deleted file]
ipf/legacy_orm/orm/validator/range.php [deleted file]
ipf/legacy_orm/orm/validator/readonly.php [deleted file]
ipf/legacy_orm/orm/validator/regexp.php [deleted file]
ipf/legacy_orm/orm/validator/time.php [deleted file]
ipf/legacy_orm/orm/validator/timestamp.php [deleted file]
ipf/legacy_orm/orm/validator/unique.php [deleted file]
ipf/legacy_orm/orm/validator/unsigned.php [deleted file]
ipf/legacy_orm/orm/validator/uploadto.php [deleted file]
ipf/legacy_orm/orm/validator/usstate.php [deleted file]
ipf/legacy_orm/orm/validator/verbose.php [deleted file]
src/adminmodel.php [new file with mode: 0644]
src/adminmodelinline.php [new file with mode: 0644]
src/app.php [new file with mode: 0644]
src/commands/buildmodels.php [new file with mode: 0644]
src/commands/fixtures.php [new file with mode: 0644]
src/commands/sql.php [new file with mode: 0644]
src/commands/syncdb.php [new file with mode: 0644]
src/modelform.php [new file with mode: 0644]
src/orm.php [new file with mode: 0644]
src/orm/access.php [new file with mode: 0644]
src/orm/adapter.php [new file with mode: 0644]
src/orm/cache/interface.php [new file with mode: 0644]
src/orm/collection.php [new file with mode: 0644]
src/orm/configurable.php [new file with mode: 0644]
src/orm/connection.php [new file with mode: 0644]
src/orm/connection/module.php [new file with mode: 0644]
src/orm/connection/mysql.php [new file with mode: 0644]
src/orm/connection/statement.php [new file with mode: 0644]
src/orm/connection/unitofwork.php [new file with mode: 0644]
src/orm/event.php [new file with mode: 0644]
src/orm/exception.php [new file with mode: 0644]
src/orm/exception/adapter.php [new file with mode: 0644]
src/orm/exception/connection.php [new file with mode: 0644]
src/orm/exception/locator.php [new file with mode: 0644]
src/orm/exception/mysql.php [new file with mode: 0644]
src/orm/exception/profiler.php [new file with mode: 0644]
src/orm/exception/validator.php [new file with mode: 0644]
src/orm/export.php [new file with mode: 0644]
src/orm/export/mysql.php [new file with mode: 0644]
src/orm/expression.php [new file with mode: 0644]
src/orm/expression/driver.php [new file with mode: 0644]
src/orm/expression/mysql.php [new file with mode: 0644]
src/orm/hydrator.php [new file with mode: 0644]
src/orm/hydrator/abstract.php [new file with mode: 0644]
src/orm/hydrator/arraydriver.php [new file with mode: 0644]
src/orm/hydrator/recorddriver.php [new file with mode: 0644]
src/orm/import/builder.php [new file with mode: 0644]
src/orm/import/schema.php [new file with mode: 0644]
src/orm/inflector.php [new file with mode: 0644]
src/orm/manager.php [new file with mode: 0644]
src/orm/null.php [new file with mode: 0644]
src/orm/pager.php [new file with mode: 0644]
src/orm/pager/layout.php [new file with mode: 0644]
src/orm/pager/layoutarrows.php [new file with mode: 0644]
src/orm/pager/range.php [new file with mode: 0644]
src/orm/pager/range/jumping.php [new file with mode: 0644]
src/orm/pager/range/sliding.php [new file with mode: 0644]
src/orm/query.php [new file with mode: 0644]
src/orm/query/abstract.php [new file with mode: 0644]
src/orm/query/check.php [new file with mode: 0644]
src/orm/query/condition.php [new file with mode: 0644]
src/orm/query/filter.php [new file with mode: 0644]
src/orm/query/filter/chain.php [new file with mode: 0644]
src/orm/query/filter/interface.php [new file with mode: 0644]
src/orm/query/from.php [new file with mode: 0644]
src/orm/query/groupby.php [new file with mode: 0644]
src/orm/query/having.php [new file with mode: 0644]
src/orm/query/joincondition.php [new file with mode: 0644]
src/orm/query/limit.php [new file with mode: 0644]
src/orm/query/offset.php [new file with mode: 0644]
src/orm/query/orderby.php [new file with mode: 0644]
src/orm/query/parser.php [new file with mode: 0644]
src/orm/query/part.php [new file with mode: 0644]
src/orm/query/registry.php [new file with mode: 0644]
src/orm/query/select.php [new file with mode: 0644]
src/orm/query/set.php [new file with mode: 0644]
src/orm/query/tokenizer.php [new file with mode: 0644]
src/orm/query/where.php [new file with mode: 0644]
src/orm/rawsql.php [new file with mode: 0644]
src/orm/record.php [new file with mode: 0644]
src/orm/record/abstract.php [new file with mode: 0644]
src/orm/record/iterator.php [new file with mode: 0644]
src/orm/relation.php [new file with mode: 0644]
src/orm/relation/association.php [new file with mode: 0644]
src/orm/relation/foreignkey.php [new file with mode: 0644]
src/orm/relation/localkey.php [new file with mode: 0644]
src/orm/relation/nest.php [new file with mode: 0644]
src/orm/relation/parser.php [new file with mode: 0644]
src/orm/table.php [new file with mode: 0644]
src/orm/table/repository.php [new file with mode: 0644]
src/orm/template.php [new file with mode: 0644]
src/orm/template/listener/orderable.php [new file with mode: 0644]
src/orm/template/listener/owned.php [new file with mode: 0644]
src/orm/template/listener/sluggable.php [new file with mode: 0644]
src/orm/template/listener/timestampable.php [new file with mode: 0644]
src/orm/template/orderable.php [new file with mode: 0644]
src/orm/template/owned.php [new file with mode: 0644]
src/orm/template/sluggable.php [new file with mode: 0644]
src/orm/template/timestampable.php [new file with mode: 0644]
src/orm/transaction.php [new file with mode: 0644]
src/orm/transaction/mysql.php [new file with mode: 0644]
src/orm/utils.php [new file with mode: 0644]
src/orm/validator.php [new file with mode: 0644]
src/orm/validator/country.php [new file with mode: 0644]
src/orm/validator/creditcard.php [new file with mode: 0644]
src/orm/validator/date.php [new file with mode: 0644]
src/orm/validator/driver.php [new file with mode: 0644]
src/orm/validator/email.php [new file with mode: 0644]
src/orm/validator/exclude.php [new file with mode: 0644]
src/orm/validator/file.php [new file with mode: 0644]
src/orm/validator/future.php [new file with mode: 0644]
src/orm/validator/html.php [new file with mode: 0644]
src/orm/validator/htmlcolor.php [new file with mode: 0644]
src/orm/validator/image.php [new file with mode: 0644]
src/orm/validator/ip.php [new file with mode: 0644]
src/orm/validator/minlength.php [new file with mode: 0644]
src/orm/validator/nospace.php [new file with mode: 0644]
src/orm/validator/notblank.php [new file with mode: 0644]
src/orm/validator/notnull.php [new file with mode: 0644]
src/orm/validator/past.php [new file with mode: 0644]
src/orm/validator/range.php [new file with mode: 0644]
src/orm/validator/readonly.php [new file with mode: 0644]
src/orm/validator/regexp.php [new file with mode: 0644]
src/orm/validator/time.php [new file with mode: 0644]
src/orm/validator/timestamp.php [new file with mode: 0644]
src/orm/validator/unique.php [new file with mode: 0644]
src/orm/validator/unsigned.php [new file with mode: 0644]
src/orm/validator/uploadto.php [new file with mode: 0644]
src/orm/validator/usstate.php [new file with mode: 0644]
src/orm/validator/verbose.php [new file with mode: 0644]

diff --git a/ipf/legacy_orm/adminmodel.php b/ipf/legacy_orm/adminmodel.php
deleted file mode 100644 (file)
index 75d0b94..0000000
+++ /dev/null
@@ -1,613 +0,0 @@
-<?php
-
-use \PFF\HtmlBuilder\Text as Text;
-use \PFF\HtmlBuilder\Tag as Tag;
-
-class IPF_Admin_Model extends IPF_Admin_Component
-{
-    public $modelName;
-
-    public function __construct($modelName)
-    {
-        $this->modelName = $modelName;
-    }
-
-    public function verbose_name()
-    {
-        return IPF_Utils::humanTitle($this->modelName);
-    }
-
-    public function slug()
-    {
-        return strtolower($this->modelName);
-    }
-
-    private function table()
-    {
-        return IPF_ORM::getTable($this->modelName);
-    }
-
-    protected function query($searchValue, $filters)
-    {
-        if (method_exists($this->modelName, 'ordering')) {
-            $m = new $this->modelName;
-            $ord = $m->ordering();
-        } elseif ($this->table()->getOrdering()) {
-            $ord = implode(', ', $this->table()->getOrdering());
-        } elseif ($this->table()->hasTemplate('IPF_ORM_Template_Orderable')) {
-            $ord = $this->table()->getTemplate('IPF_ORM_Template_Orderable')->getColumnName();
-        } else {
-            $ord = '1 desc';
-        }
-
-        $q = IPF_ORM_Query::create()->from($this->modelName)->orderby($ord);
-
-        if ($searchValue) {
-            $wh = array();
-            $whv = array();
-            foreach ($this->_searchFields() as $f) {
-                $wh[] = $f.' like ?';
-                $whv[] = '%'.$searchValue.'%';
-            }
-            $q->addWhere($wh, $whv);
-        }
-
-        foreach ($filters as $f)
-            $f->applyToQuery($q);
-
-        return $q;
-    }
-
-    protected function getItems($searchValue, $filters, $page, $pageSize)
-    {
-        $idColumn = $this->table()->getIdentifier();
-
-        $result = array();
-        foreach ($this->query($searchValue, $filters)->limit($pageSize)->offset(($page - 1) * $pageSize)->execute() as $o) {
-            $id = $o->__get($idColumn);
-            $result[$id] = $o;
-        }
-        return $result;
-    }
-
-    protected function itemsCount($searchValue, $filters)
-    {
-        return $this->query($searchValue, $filters)->count();
-    }
-
-    public function getObjectByID($id)
-    {
-        return $this->table()->find($id);
-    }
-
-    public function searcheable()
-    {
-        return count($this->_searchFields()) > 0;
-    }
-
-    protected function _searchFields()
-    {
-        return array();
-    }
-
-    public function list_display()
-    {
-        return $this->table()->getColumnNames();
-    }
-
-    public function renderCell($object, $column)
-    {
-        $value = $object->$column;
-        switch ($object->getTable()->getTypeOf($column)) {
-            case 'boolean':
-                return $value
-                    ? '<span class="positive">&#x2714;</span>'
-                    : '<span class="negative">&#x2718;</span>';
-            case 'timestamp':
-                return Text::escape(IPF_Utils::formatDate($value));
-            default:
-                return Text::escape($value);
-        }
-    }
-
-    protected function columnTitle($column)
-    {
-        $columns = $this->table()->getColumns();
-        if (array_key_exists($column, $columns) && array_key_exists('verbose', $columns[$column]))
-            return $columns[$column]['verbose'];
-        else
-            return parent::columnTitle($column);
-    }
-
-    public function _orderable()
-    {
-        return $this->_orderableColumn() !== null;
-    }
-
-    public function _orderableColumn()
-    {
-        if (method_exists($this, 'list_order'))
-            return $this->list_order();
-        elseif ($this->table()->hasTemplate('IPF_ORM_Template_Orderable'))
-            return $this->table()->getTemplate('IPF_ORM_Template_Orderable')->getColumnName();
-        else
-            return null;
-    }
-
-    /* filters */
-
-    protected function _listFilters()
-    {
-        return array();
-    }
-
-    public function listFilters()
-    {
-        $filters = array();
-        foreach ($this->_listFilters() as $f) {
-            if (is_string($f))
-                $f = new IPF_Admin_Model_RelationFilter($this->modelName, $f);
-
-            $filters[] = $f;
-        }
-        return $filters;
-    }
-
-    public function reorder($ids)
-    {
-        if (!$this->_orderable())
-            return false;
-
-        $ord_field = $this->_orderableColumn();
-
-        $table = IPF_ORM::getTable($this->modelName);
-        $conn = $table->getConnection();
-
-        $idColumn = $table->getIdentifier();
-        if (is_array($idColumn))
-            $idColumn = $idColumn[0];
-
-        $questions = str_repeat('?,', count($ids)-1) . '?';
-        $query = 'SELECT ' . $conn->quoteIdentifier($ord_field) .
-            ' FROM ' . $conn->quoteIdentifier($table->getTableName()) .
-            ' WHERE ' . $conn->quoteIdentifier($idColumn) . ' IN (' . $questions . ')' .
-            ' ORDER BY ' . $conn->quoteIdentifier($ord_field);
-        $ords = $conn->fetchColumn($query, $ids);
-
-        $i = 0;
-        foreach ($ids as $id) {
-            $item = $table->find($id);
-            $item[$ord_field] = $ords[$i];
-            $item->save();
-            $i++;
-        }
-
-        return true;
-    }
-
-    /* edit */
-
-    protected function _getForm($model, $data)
-    {
-        $extra = array(
-            'user_fields' => $this->fields(),
-            'inlines' => $this->inlines(),
-        );
-        return new IPF_Admin_ModelForm($data, $model, $this->modelName, $extra);
-    }
-
-    public function inlines()
-    {
-        return array();
-    }
-
-    public function saveObject($form, $model)
-    {
-        $model = $form->save();
-        $idColumn = $this->table()->getIdentifier();
-        return array($model->$idColumn, $model);
-    }
-
-    public function deleteObject($model)
-    {
-        $model->delete();
-    } 
-
-    public function fields()
-    {
-        return null;
-    }
-}
-
-class IPF_Admin_ModelForm extends IPF_Form_Model
-{
-    private $inlines = array();
-
-    function __construct($data=null, $model=null, $modelClass='', $extra=array())
-    {
-        $editMode = $model !== null;
-
-        if (!$model)
-            $model = new $modelClass;
-
-        $extra['model'] = $model;
-
-        if ($extra['inlines']) {
-            foreach ($extra['inlines'] as $inlineClassName) {
-                $this->inlines[] = new $inlineClassName($model);
-            }
-        }
-
-        if ($editMode) {
-            $extra['initial'] = $this->getFormData($model, $this->inlines);
-        }
-
-        parent::__construct($data, $extra);
-    }
-
-    function initFields($extra=array())
-    {
-        parent::initFields($extra);
-
-        if ($this->inlines) {
-            $this->field_groups[] = array('fields' => array_keys($this->fields));
-        }
-
-        foreach ($this->inlines as $inline) {
-            $name = $inline->getModelName();
-            $this->fields[$name] = $inline->createField();
-
-            $this->field_groups[] = array(
-                'label' => $inline->getLegend(),
-                'fields' => array($name),
-            );
-        }
-    }
-
-    private function getFormData($o, $inlines)
-    {
-        $data = $o->getData();
-        foreach ($o->getTable()->getRelations() as $rname => $rel) {
-            $fields = $this->fields();
-            if (!$fields || in_array($rname, $fields)) {
-                if ($rel->getType() == IPF_ORM_Relation::MANY_AGGREGATE) {
-                    $data[$rname] = array();
-                    foreach ($rel->fetchRelatedFor($o) as $ri) {
-                        $data[$rname][] = $ri->pk();
-                    }
-                }
-            }
-        }
-
-        foreach ($inlines as $inline) {
-            $objs = array();
-            foreach ($inline->getObjects() as $io) {
-                $d = $io->getData();
-                $d['id'] = $io->pk();
-                $objs[] = $d;
-            }
-            $data[$inline->getModelName()] = $objs;
-        }
-
-        return $data;
-    }
-
-    function save($commit=true)
-    {
-        $model = parent::save($commit);
-
-        foreach ($this->inlines as $inline) {
-            $this->saveInlineObject($inline, $model);
-        }
-
-        return $model;
-    }
-
-    private function saveInlineObject($inline, $model)
-    {
-        $objIndex = array();
-        foreach ($inline->getObjects() as $obj)
-            $objIndex[$obj->pk()] = $obj;
-
-        $modelName = $inline->getModelName();
-        $fk_local = $inline->getFkLocal();
-
-        foreach ($this->cleaned_data[$modelName] as $objData) {
-            if (array_key_exists('id', $objData) && array_key_exists($objData['id'], $objIndex)) {
-                $obj = $objIndex[$objData['id']];
-                if (isset($objData['is_remove'])) {
-                    $obj->delete();
-                } else {
-                    $obj->synchronizeWithArray($objData);
-                    $obj->save();
-                }
-            } else {
-                $objData[$fk_local] = $model->id;
-                $obj = new $modelName;
-                $obj->synchronizeWithArray($objData);
-                $obj->save();
-            }
-        }
-    }
-}
-
-abstract class IPF_Admin_Model_Filter implements IPF_Admin_ListFilter
-{
-    abstract function applyToQuery($q);
-
-    protected $title, $paramName;
-
-    function title()
-    {
-        return $this->title;
-    }
-
-    protected function link($params, $value, $title)
-    {
-        if ($value !== null)
-            $params[$this->paramName] = $value;
-        else
-            unset($params[$this->paramName]);
-
-        return Tag::a()
-            ->attr('href', '?'.IPF_HTTP_URL::generateParams($params, false))
-            ->append($title);
-    }
-}
-
-class IPF_Admin_Model_RelationFilter extends IPF_Admin_Model_Filter
-{
-    private $relation, $selected = null;
-
-    function __construct($modelName, $relName)
-    {
-        $this->relation = IPF_ORM::getTable($modelName)->getRelation($relName);
-        $this->title = 'By '.IPF_Utils::humanTitle($relName);
-
-        $this->paramName = 'filter_'.$this->relation['local'];
-    }
-
-    function setParams($params)
-    {
-        $this->selected = \PFF\Arr::get($params, $this->paramName);
-    }
-
-    function applyToQuery($q)
-    {
-        // check ???
-        if ($this->selected)
-            $q->addWhere($this->relation['local'].' = ?', array($this->selected));
-    }
-
-    function render($extraParams)
-    {
-        $ul = Tag::ul();
-
-        // reset filter
-        $ul->append(Tag::li()
-            ->toggleClass('selected', !$this->selected)
-            ->append($this->link($extraParams, null, __('All'))));
-
-        // query related
-        $table = IPF_ORM::getTable($this->relation['class']);
-
-        $query = $table->createQuery();
-        if ($table->getOrdering()) {
-            $query->orderBy(implode(', ', $table->getOrdering()));
-        } elseif ($table->hasTemplate('IPF_ORM_Template_Orderable')) {
-            $query->orderBy($table->getTemplate('IPF_ORM_Template_Orderable')->getColumnName());
-        }
-
-        $foreign = $this->relation['foreign'];
-        foreach ($query->execute() as $val) {
-            $id = $val[$foreign];
-            $name = (string)$val;
-
-            $ul->append(Tag::li()
-                ->toggleClass('selected', $this->selected == $id)
-                ->append($this->link($extraParams, $id, $name)));
-        }
-
-        return $ul->html();
-    }
-}
-
-class IPF_Admin_Model_OwnedFilter extends IPF_Admin_Model_Filter
-{
-    private $column, $selected = null;
-
-    function __construct($modelName, $column, $title)
-    {
-        $this->column = $column;
-        $this->title = $title;
-        $this->paramName = 'filter_'.$this->column;
-    }
-
-    function setParams($params)
-    {
-        $this->selected = \PFF\Arr::get($params, $this->paramName);
-    }
-
-    function applyToQuery($q)
-    {
-        // check ???
-        if ($this->selected)
-            $q->addWhere($this->column.' = ?', array($this->selected));
-    }
-
-    function render($extraParams)
-    {
-        $ul = Tag::ul();
-
-        // reset filter
-        $ul->append(Tag::li()
-            ->toggleClass('selected', !$this->selected)
-            ->append($this->link($extraParams, null, __('All'))));
-
-        foreach (User::query()->fetchAll() as $val) {
-            $id = $val->id;
-            $name = (string)$val;
-
-            $ul->append(Tag::li()
-                ->toggleClass('selected', $this->selected == $id)
-                ->append($this->link($extraParams, $id, $name)));
-        }
-
-        return $ul->html();
-    }
-}
-
-class BooleanFilter extends IPF_Admin_Model_Filter
-{
-    private $column, $trueTitle, $falseTitle, $selected;
-
-    public function __construct($column, $title, $trueTitle='Yes', $falseTitle='No')
-    {
-        $this->column = $column;
-        $this->title = $title;
-        $this->trueTitle = $trueTitle;
-        $this->falseTitle = $falseTitle;
-
-        $this->paramName = 'filter_'.$column;
-    }
-
-    function setParams($params)
-    {
-        $this->selected = \PFF\Arr::get($params, $this->paramName);
-    }
-
-    function render($extraParams)
-    {
-        return Tag::ul(null,
-            Tag::li()
-                ->toggleClass('selected', $this->selected !== 'y' && $this->selected !== 'n')
-                ->append($this->link($extraParams, null, __('All'))),
-            Tag::li()
-                ->toggleClass('selected', $this->selected === 'y')
-                ->append($this->link($extraParams, 'y', $this->trueTitle)),
-            Tag::li()
-                ->toggleClass('selected', $this->selected === 'n')
-                ->append($this->link($extraParams, 'n', $this->falseTitle)));
-    }
-
-    function applyToQuery($q)
-    {
-        switch ($this->selected) {
-            case 'y':
-                $query->addWhere($this->column);
-                break;
-            case 'n':
-                $query->addWhere('NOT '.$this->column);
-                break;
-        }
-    }
-}
-
-class DateHierarchyListFilter extends IPF_Admin_Model_Filter
-{
-    private $model, $name;
-    private $day, $month, $year;
-
-    function __construct($title, $modelName, $fieldName)
-    {
-        $this->title = $title;
-        $this->modelName = $modelName;
-        $this->name = $fieldName;
-
-        $this->paramName = $fieldName;
-    }
-
-    function setParams($params)
-    {
-        $date = \PFF\Arr::get($params, $this->paramName, '');
-        $matches = array();
-        if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $date, $matches)) {
-            $this->year = intval($matches[1]);
-            $this->month = intval($matches[2]);
-            $this->day = intval($matches[3]);
-        }
-    }
-
-    function render($extraParams)
-    {
-        $ul = Tag::ul();
-
-        $ul->append(Tag::li()
-            ->toggleClass('selected', !$this->year)
-            ->append($this->link($extraParams, null, __('All'))));
-
-        if ($this->year)
-            $ul->append($this->renderChoice($extraParams, $this->year, 0, 0, $this->year)->addClass('selected'));
-
-        if ($this->month)
-            $ul->append($this->renderChoice($extraParams, $this->year, $this->month, 0, $this->monthName($this->month))->addClass('selected'));
-
-        if ($this->day)
-            $ul->append($this->renderChoice($extraParams, $this->year, $this->month, $this->day, $this->day)->addClass('selected'));
-
-        if ($this->day) {
-        } elseif ($this->month) {
-            $days = $this->choices('DAY(' . $this->name . ')',
-                'YEAR(' . $this->name . ')', $this->year,
-                'MONTH(' . $this->name . ')', $this->month);
-            foreach ($days as $day) {
-                $ul->append($this->renderChoice($extraParams, $this->year, $this->month, $day, $day));
-            }
-        } elseif ($this->year) {
-            $months = $this->choices('MONTH(' . $this->name . ')',
-                'YEAR(' . $this->name . ')', $this->year);
-            foreach ($months as $month) {
-                $ul->append($this->renderChoice($extraParams, $this->year, $month, 0, $this->monthName($month)));
-            }
-        } else {
-            $years = $this->choices('YEAR(' . $this->name . ')');
-            foreach ($years as $year) {
-                $ul->append($this->renderChoice($extraParams, $year, 0, 0, $year));
-            }
-        }
-
-        return $ul->html();
-    }
-
-    function applyToQuery($q)
-    {
-        if ($this->day)
-            $q->addWhere('DAY(' . $this->name . ') = ?', array($this->day));
-        if ($this->month)
-            $q->addWhere('MONTH(' . $this->name . ') = ?', array($this->month));
-        if ($this->year)
-            $q->addWhere('YEAR(' . $this->name . ') = ?', array($this->year));
-    }
-
-    private function choices(/* $what, [$expr, $value, ...]*/)
-    {
-        $args = func_get_args();
-        $what = array_shift($args);
-
-        $q = IPF_ORM_Query::create()
-            ->from($this->modelName)
-            ->select($what . ' AS value')
-            ->groupBy('1')
-            ->orderBy('1');
-
-        while ($args) {
-            $expr = array_shift($args);
-            $value = array_shift($args);
-
-            $q->addWhere($expr . ' = ?', array($value));
-        }
-        return \PFF\Arr::create($q->fetchArray())->pluck('value')->arr();
-    }
-
-    private function renderChoice($extraParams, $year, $month, $day, $label)
-    {
-        return Tag::li(null,
-            $this->link($extraParams, sprintf('%04d-%02d-%02d', $year, $month, $day), $label));
-    }
-
-    protected function monthName($month)
-    {
-        return date('F', mktime(0, 0, 0, $month, 1));
-    }
-}
-
diff --git a/ipf/legacy_orm/adminmodelinline.php b/ipf/legacy_orm/adminmodelinline.php
deleted file mode 100644 (file)
index bb5e4dd..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-abstract class IPF_Admin_ModelInline
-{
-    public $parentModel = null;
-    public $orderby = 'id';
-
-    function __construct($parentModel)
-    {
-        $this->parentModel = $parentModel;
-    }
-
-    abstract function getModelName();
-
-    public function getApplication()
-    {
-        foreach (IPF_Project::getInstance()->appList() as $app)
-            foreach (IPF_Legacy_ORM_App::appModelList($app) as $model)
-                if ($model == $this->getModelName())
-                    return $app;
-        return null;
-    }
-
-    function getAddNum()
-    {
-        return 4;
-    }
-
-    function getLegend()
-    {
-        return $this->getModelName();
-    }
-
-    function getFkName()
-    {
-        foreach($this->getTable()->getRelations() as $rel) {
-            if ($rel->getClass()==get_class($this->parentModel))
-                return $rel->getAlias();
-        }
-        throw new IPF_Exception(__('Cannot get fkName for '.$this->getModelName()));
-    }
-
-    function getFkLocal()
-    {
-        foreach($this->getTable()->getRelations() as $rel) {
-            if ($rel->getClass() == get_class($this->parentModel))
-                return $rel->getLocal();
-        }
-        throw new IPF_Exception(__('Cannot get fkLocal for '.$this->getModelName()));
-    }
-
-    function getObjects()
-    {
-        if (!$this->parentModel->exists())
-            return array();
-
-        $query = IPF_ORM_Query::create()
-            ->from($this->getModelName())
-            ->where($this->getFkLocal().'='.$this->parentModel->id);
-
-        $o = $this->_orderableColumn();
-        if ($o)
-            $query->orderby($o);
-        else
-            $query->orderby($this->orderby);
-
-        return $query->execute();
-    }
-
-    public function _orderable()
-    {
-        return $this->_orderableColumn() !== null;
-    }
-
-    public function _orderableColumn()
-    {
-        if ($this->getTable()->hasTemplate('IPF_ORM_Template_Orderable'))
-            return $this->getTable()->getTemplate('IPF_ORM_Template_Orderable')->getColumnName();
-        else
-            return null;
-    }
-
-    private function getTable()
-    {
-        return IPF_ORM::getTable($this->getModelName());
-    }
-
-    public function createField()
-    {
-        $exclude = array(
-            $this->getFkName(),
-            $this->getFkLocal(),
-        );
-        $o = $this->_orderableColumn();
-        if ($o)
-            $exclude[] = $o;
-
-        $fields = array();
-        foreach (IPF_Form_Model::suggestFields($this->getTable(), null, $exclude, null) as $field) {
-            list($n, $f) = $field;
-            $fields[$n] = $f;
-        }
-
-        return new IPF_Form_Field_Set(array(
-            'fields' => $fields,
-            'addCount' => $this->getAddNum(),
-        ));
-    }
-}
-
diff --git a/ipf/legacy_orm/app.php b/ipf/legacy_orm/app.php
deleted file mode 100644 (file)
index 8db81a0..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-class IPF_Legacy_ORM_App extends IPF_Application
-{
-    private static $appModels = array();
-
-    public static function appModelList($app)
-    {
-        if (!array_key_exists($app->getName(), self::$appModels)) {
-            $models = array();
-            try {
-                $it = new DirectoryIterator($app->getPath().DIRECTORY_SEPARATOR.'models');
-                foreach ($it as $file) {
-                    $e = explode('.', $file->getFileName(), 2);
-                    if (count($e) == 2 && $e[1] === 'php') {
-                        $models[] = $e[0];
-                    }
-                }
-            } catch (RuntimeException $e) {
-                // nothing to do
-            }
-
-            self::$appModels[$app->getName()] = $models;
-        }
-        return self::$appModels[$app->getName()];
-    }
-
-    public function commands()
-    {
-        return array(
-            new IPF_Legacy_ORM_Command_BuildModels,
-            new IPF_Legacy_ORM_Command_Sql,
-            new IPF_Legacy_ORM_Command_SyncDB,
-            new IPF_Legacy_ORM_Command_Fixtures,
-        );
-    }
-}
-
diff --git a/ipf/legacy_orm/commands/buildmodels.php b/ipf/legacy_orm/commands/buildmodels.php
deleted file mode 100644 (file)
index 9dca874..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-class IPF_Legacy_ORM_Command_BuildModels
-{
-    public $command = 'buildmodels';
-    public $description = 'Build all model classes';
-
-    public function run($args=null)
-    {
-        print "Build all model classes\n\n";
-
-        $project = IPF_Project::getInstance();
-
-        $paths = array();
-        foreach ($project->appList() as $app)
-            $paths[] = $app->getPath();
-
-        IPF_ORM::generateModelsFromYaml($paths, array());
-    }
-}
-
diff --git a/ipf/legacy_orm/commands/fixtures.php b/ipf/legacy_orm/commands/fixtures.php
deleted file mode 100644 (file)
index 3c6e279..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-class IPF_Legacy_ORM_Command_Fixtures
-{
-    public $command = 'fixtures';
-    public $description = 'Load fixtures into database';
-
-    public function run($args=null)
-    {
-        print "Load project fixtures to database\n";
-
-        $project = IPF_Project::getInstance();
-
-        $paths = array(IPF::get('project_path'));
-        foreach ($project->appList() as $app)
-            $paths[] = $app->path;
-
-        $fixtures = array();
-        foreach ($paths as $path) {
-            $path .= DIRECTORY_SEPARATOR.'fixtures.php';
-            if (is_file($path))
-                $fixtures = array_merge($fixtures, require $path);
-        }
-
-        if (!count($fixtures)) {
-            echo "No fixtures found\n";
-            return;
-        }
-
-        foreach ($fixtures as $fixture) {
-            $modelClass = $fixture['model'];
-            $key = $fixture['key'];
-            if (!is_array($key))
-                $key = array($key);
-            $records = $fixture['records'];
-            echo "Loading $modelClass ";
-            $table = IPF_ORM::getTable($modelClass);
-            $table->getConnection()->beginTransaction();
-
-            $query = $table
-                ->createQuery()
-                ->limit(1);
-            foreach ($key as $k)
-                $query->addWhere($k . ' = ?');
-
-            foreach ($records as $record) {
-                $params = array();
-                foreach ($key as $k)
-                    $params[] = $record[$k];
-
-                $model = $query->execute($params);
-                if ($model)
-                    $model = $model[0];
-                else
-                    $model = new $modelClass;
-
-                foreach ($record as $k => $v)
-                    $model->$k = $v;
-
-                $model->save();
-                echo '.';
-            }
-            $table->getConnection()->commit();
-            echo "\n";
-        }
-    }
-}
-
diff --git a/ipf/legacy_orm/commands/sql.php b/ipf/legacy_orm/commands/sql.php
deleted file mode 100644 (file)
index 31f55c9..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-
-class IPF_Legacy_ORM_Command_Sql
-{
-    public $command = 'sql';
-    public $description = 'Show all SQL DDL from model classes';
-
-    public function run($args=null)
-    {
-        print "Show all SQL DDL from model classes\n";
-
-        foreach (IPF_Project::getInstance()->appList() as $app)
-            print IPF_ORM::generateSqlFromModels($app)."\n";
-    }
-}
-
diff --git a/ipf/legacy_orm/commands/syncdb.php b/ipf/legacy_orm/commands/syncdb.php
deleted file mode 100644 (file)
index 02b550f..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-class IPF_Legacy_ORM_Command_SyncDB
-{
-    public $command = 'syncdb';
-    public $description = 'Create tables from model classes';
-
-    public function run($args=null)
-    {
-        print "Create tables from model classes\n";
-
-        $project = IPF_Project::getInstance();
-
-        $models = array();
-        foreach ($project->appList() as $app)
-            $models = array_merge($models, IPF_Legacy_ORM_App::appModelList($app));
-
-        IPF_ORM::createTablesFromModels($models);
-    }
-}
-
diff --git a/ipf/legacy_orm/modelform.php b/ipf/legacy_orm/modelform.php
deleted file mode 100644 (file)
index fe9cb93..0000000
+++ /dev/null
@@ -1,223 +0,0 @@
-<?php
-
-class IPF_Form_Model extends IPF_Form
-{
-    public $model = null;
-    public $user_fields = null;
-
-    function initFields($extra=array())
-    {
-        if (isset($extra['model']))
-            $this->model = $extra['model'];
-        else
-            throw new IPF_Exception_Form(__('Unknown model for form'));
-
-        if (isset($extra['user_fields']))
-            $this->user_fields = $extra['user_fields'];
-
-        $exclude = \PFF\Arr::get($extra, 'exclude', array());
-        $fields = self::suggestFields($this->model->getTable(), $this->fields(), $exclude, $this);
-        foreach ($fields as $field) {
-            list($n, $f) = $field;
-            $this->fields[$n] = $f;
-        }
-    }
-
-    public static function suggestFields($table, $fields=null, $exclude=array(), $form=null)
-    {
-        $result = array();
-
-        $db_columns = $table->getColumns();
-        $db_relations = $table->getRelations();
-
-        if ($fields === null) {
-            foreach ($db_columns as $name => $col) {
-                if (array_search($name, $exclude) !== false)
-                    continue;
-                if (isset($col['exclude']) && $col['exclude'])
-                    continue;
-
-                if ($form && method_exists($form, 'add__'.$name.'__field')) {
-                    $f = call_user_func(array($form, 'add__'.$name.'__field'));
-                    if ($f)
-                        $field = array($name, $f);
-                } else {
-                    $field = self::createDBField($name, $table, $col);
-                }
-
-                if ($field)
-                    $result[] = $field;
-            }
-
-            foreach ($db_relations as $name => $relation) {
-                if (array_search($name, $exclude) !== false)
-                    continue;
-                if (isset($relation['exclude']) && $relation['exclude'])
-                    continue;
-
-                if ($form && method_exists($form, 'add__'.$name.'__field')) {
-                    $f = call_user_func(array($form, 'add__'.$name.'__field'));
-                    if ($f)
-                        $field = array($name, $f);
-                } else {
-                    $field = self::createDBRelation($name, $relation, $db_columns);
-                }
-
-                if ($field)
-                    $result[] = $field;
-            }
-        } else {
-            foreach ($fields as $uname) {
-                $field = null;
-                if ($form && method_exists($form, 'add__'.$uname.'__field')) {
-                    $f = call_user_func(array($form, 'add__'.$uname.'__field'));
-                    if ($f)
-                        $field = array($uname, $f);
-                } elseif (array_key_exists($uname, $db_columns)) {
-                    $field = self::createDBField($uname, $table, $db_columns[$uname]);
-                } elseif (array_key_exists($uname, $db_relations)) {
-                    $field = self::createDBRelation($uname, $db_relations[$uname], $db_columns);
-                } else {
-                    throw new IPF_Exception_Form(sprintf(__("Model '%s' has no column '%s'."), $table->getComponentName(), $uname));
-                }
-                if ($field)
-                    $result[] = $field;
-            }
-        }
-        return $result;
-    }
-
-    public static function createDBField($name, $table, $col)
-    {
-        if ($name == $table->getIdentifier())
-            return null;
-
-        $required = isset($col['notblank']) && $col['notblank'];
-        $label = isset($col['verbose']) ? $col['verbose'] : IPF_Utils::humanTitle($name);
-
-        $params = array(
-            'required'  => $required,
-            'label'     => $label,
-            'help_text' => '',
-        );
-
-        switch ($col['type']) {
-            case 'string':
-                if (isset($col['length']))
-                    $params['max_length'] = (int)($col['length']);
-
-                if (isset($col['uploadTo']))
-                    $params['uploadTo'] = $col['uploadTo'];
-
-                if     (isset($col['email']) && $col['email'])  { $form_field = new IPF_Form_Field_Email($params); }
-                elseif (isset($col['file'])  && $col['file'])   { $form_field = new IPF_Form_Field_File($params); }
-                elseif (isset($col['image']) && $col['image'])  { $form_field = new IPF_Form_Field_Image($params); }
-                elseif (isset($col['html'])  && $col['html'])   { $form_field = new IPF_Form_Field_Html($params); }
-                else                                            { $form_field = new IPF_Form_Field_Varchar($params); }
-
-                break;
-            case 'boolean':
-                $form_field = new IPF_Form_Field_Boolean($params);
-                break;
-            case 'integer':
-                $params['widget_attrs'] = array('style' => 'width:140px;');
-                $form_field = new IPF_Form_Field_Integer($params);
-                break;
-            case 'double':
-            case 'decimal':
-                $params['widget_attrs'] = array('style' => 'width:140px;');
-                $form_field = new IPF_Form_Field_Float($params);
-                break;
-            case 'date':
-                $format = IPF::get('date_format');
-                if ($format)
-                    $params['widget_attrs'] = array('format' => $format);
-                $form_field = new IPF_Form_Field_Date($params);
-                break;
-            case 'datetime':
-            case 'timestamp':
-                $format = IPF::get('datetime_format');
-                if ($format)
-                    $params['widget_attrs'] = array('format' => $format);
-                $form_field = new IPF_Form_Field_Datetime($params);
-                break;
-            default:
-                throw new IPF_Exception_Form(__('Unsupported column type \''.$col['type'].'\'.'));
-        }
-
-        if ($form_field !== null)
-            return array($name, $form_field);
-        else
-            return null;
-    }
-
-    public static function createDBRelation($name, $relation, $db_columns)
-    {
-        $rt = $relation->getType();
-        if ($rt !== IPF_ORM_Relation::ONE_AGGREGATE && $rt !== IPF_ORM_Relation::MANY_AGGREGATE)
-            return null;
-
-        $lfn = $relation->getLocalFieldName();
-        if (isset($db_columns[$lfn]))
-            $col = $db_columns[$lfn];
-        else
-            $col = array();
-
-        $table = IPF_ORM::getTable($relation->getClass());
-
-        $params = array(
-            'required'  => isset($col['notblank']),
-            'label'     => isset($col['verbose']) ? $col['verbose'] : IPF_Utils::humanTitle($name),
-            'help_text' => '',
-            'table'     => $table,
-            'model'     => $relation->getClass(),
-        );
-
-        if ($rt === IPF_ORM_Relation::ONE_AGGREGATE) {
-            $choices = array('--------' => '');
-            $pk = $table->getIdentifier();
-            foreach ($table->findAll() as $o) {
-                $choices[$o->__toString()] = $o->$pk;
-            }
-
-            $params['choices'] = $choices;
-            return array($name.'_id', new IPF_Form_Field_Choice($params));
-        } elseif ($rt === IPF_ORM_Relation::MANY_AGGREGATE) {
-            $choices = array();
-            $pk = $table->getIdentifier();
-            foreach ($table->findAll() as $o) {
-                $choices[$o->__toString()] = $o->$pk;
-            }
-
-            $params['choices'] = $choices;
-            return array($name, new IPF_Form_Field_MultipleChoice($params));
-        } else {
-            return null;
-        }
-    }
-
-    function fields()
-    {
-        return $this->user_fields;
-    }
-
-    function save($commit=true)
-    {
-        if (!$this->isValid())
-            throw new IPF_Exception_Form(__('Cannot save the model from an invalid form.'));
-
-        $this->model->SetFromFormData($this->cleaned_data);
-        $this->model->save();
-        $rels = $this->model->getTable()->getRelations();
-        foreach ($rels as $rname => $rel) {
-            if (isset($this->cleaned_data[$rname])) {
-                $this->model->unlink($rel->getAlias());
-                if (is_array($this->cleaned_data[$rname])) {
-                    $this->model->link($rel->getAlias(),$this->cleaned_data[$rname]);
-                }
-            }
-        }
-        return $this->model;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm.php b/ipf/legacy_orm/orm.php
deleted file mode 100644 (file)
index f5fb916..0000000
+++ /dev/null
@@ -1,255 +0,0 @@
-<?php
-
-final class IPF_ORM
-{
-    const ERR                       = -1;
-    const ERR_SYNTAX                = -2;
-    const ERR_CONSTRAINT            = -3;
-    const ERR_NOT_FOUND             = -4;
-    const ERR_ALREADY_EXISTS        = -5;
-    const ERR_UNSUPPORTED           = -6;
-    const ERR_MISMATCH              = -7;
-    const ERR_INVALID               = -8;
-    const ERR_NOT_CAPABLE           = -9;
-    const ERR_TRUNCATED             = -10;
-    const ERR_INVALID_NUMBER        = -11;
-    const ERR_INVALID_DATE          = -12;
-    const ERR_DIVZERO               = -13;
-    const ERR_NODBSELECTED          = -14;
-    const ERR_CANNOT_CREATE         = -15;
-    const ERR_CANNOT_DELETE         = -16;
-    const ERR_CANNOT_DROP           = -17;
-    const ERR_NOSUCHTABLE           = -18;
-    const ERR_NOSUCHFIELD           = -19;
-    const ERR_NEED_MORE_DATA        = -20;
-    const ERR_NOT_LOCKED            = -21;
-    const ERR_VALUE_COUNT_ON_ROW    = -22;
-    const ERR_INVALID_DSN           = -23;
-    const ERR_CONNECT_FAILED        = -24;
-    const ERR_EXTENSION_NOT_FOUND   = -25;
-    const ERR_NOSUCHDB              = -26;
-    const ERR_ACCESS_VIOLATION      = -27;
-    const ERR_CANNOT_REPLACE        = -28;
-    const ERR_CONSTRAINT_NOT_NULL   = -29;
-    const ERR_DEADLOCK              = -30;
-    const ERR_CANNOT_ALTER          = -31;
-    const ERR_MANAGER               = -32;
-    const ERR_MANAGER_PARSE         = -33;
-    const ERR_LOADMODULE            = -34;
-    const ERR_INSUFFICIENT_DATA     = -35;
-    const ERR_CLASS_NAME            = -36;
-
-    const CASE_LOWER = 2;
-    const CASE_NATURAL = 0;
-    const CASE_UPPER = 1;
-    const CURSOR_FWDONLY = 0;
-    const CURSOR_SCROLL = 1;
-    const ERRMODE_EXCEPTION = 2;
-    const ERRMODE_SILENT = 0;
-    const ERRMODE_WARNING = 1;
-    const FETCH_ASSOC = 2;
-    const FETCH_BOTH = 4;
-    const FETCH_BOUND = 6;
-    const FETCH_CLASS = 8;
-    const FETCH_CLASSTYPE = 262144;
-    const FETCH_COLUMN = 7;
-    const FETCH_FUNC = 10;
-    const FETCH_GROUP = 65536;
-    const FETCH_INTO = 9;
-    const FETCH_LAZY = 1;
-    const FETCH_NAMED = 11;
-    const FETCH_NUM = 3;
-    const FETCH_OBJ = 5;
-    const FETCH_ORI_ABS = 4;
-    const FETCH_ORI_FIRST = 2;
-    const FETCH_ORI_LAST = 3;
-    const FETCH_ORI_NEXT = 0;
-    const FETCH_ORI_PRIOR = 1;
-    const FETCH_ORI_REL = 5;
-    const FETCH_SERIALIZE = 524288;
-    const FETCH_UNIQUE = 196608;
-    const NULL_EMPTY_STRING = 1;
-    const NULL_NATURAL = 0;
-    const NULL_TO_STRING         = NULL;
-    const PARAM_BOOL = 5;
-    const PARAM_INPUT_OUTPUT = -2147483648;
-    const PARAM_INT = 1;
-    const PARAM_LOB = 3;
-    const PARAM_NULL = 0;
-    const PARAM_STMT = 4;
-    const PARAM_STR = 2;
-
-    const ATTR_AUTOCOMMIT           = 0;
-    const ATTR_PREFETCH             = 1;
-    const ATTR_TIMEOUT              = 2;
-    const ATTR_ERRMODE              = 3;
-    const ATTR_SERVER_VERSION       = 4;
-    const ATTR_CLIENT_VERSION       = 5;
-    const ATTR_SERVER_INFO          = 6;
-    const ATTR_CONNECTION_STATUS    = 7;
-    const ATTR_CASE                 = 8;
-    const ATTR_CURSOR_NAME          = 9;
-    const ATTR_CURSOR               = 10;
-    const ATTR_ORACLE_NULLS         = 11;
-    const ATTR_PERSISTENT           = 12;
-    const ATTR_STATEMENT_CLASS      = 13;
-    const ATTR_FETCH_TABLE_NAMES    = 14;
-    const ATTR_FETCH_CATALOG_NAMES  = 15;
-    const ATTR_STRINGIFY_FETCHES    = 17;
-    const ATTR_MAX_COLUMN_LEN       = 18;
-
-    const ATTR_FIELD_CASE           = 102;
-    const ATTR_CMPNAME_FORMAT       = 118;
-    const ATTR_DBNAME_FORMAT        = 117;
-    const ATTR_TBLCLASS_FORMAT      = 119;
-    const ATTR_EXPORT               = 140;
-    const ATTR_DECIMAL_PLACES       = 141;
-
-    const ATTR_PORTABILITY          = 106;
-    const ATTR_COLL_KEY             = 108;
-    const ATTR_DEFAULT_TABLE_TYPE   = 112;
-    const ATTR_DEF_TEXT_LENGTH      = 113;
-    const ATTR_DEF_VARCHAR_LENGTH   = 114;
-    const ATTR_DEF_TABLESPACE       = 115;
-    const ATTR_EMULATE_DATABASE     = 116;
-    const ATTR_USE_NATIVE_ENUM      = 117;
-
-    //const ATTR_FETCHMODE                = 118;
-    const ATTR_NAME_PREFIX              = 121;
-    const ATTR_CREATE_TABLES            = 122;
-    const ATTR_COLL_LIMIT               = 123;
-
-    const ATTR_CACHE                    = 150;
-    const ATTR_RESULT_CACHE             = 150;
-    const ATTR_CACHE_LIFESPAN           = 151;
-    const ATTR_RESULT_CACHE_LIFESPAN    = 151;
-    const ATTR_LOAD_REFERENCES          = 153;
-    const ATTR_DEFAULT_PARAM_NAMESPACE  = 156;
-    const ATTR_QUERY_CACHE              = 157;
-    const ATTR_QUERY_CACHE_LIFESPAN     = 158;
-    const ATTR_MODEL_LOADING            = 161;
-    const ATTR_RECURSIVE_MERGE_FIXTURES = 162;
-    const ATTR_SINGULARIZE_IMPORT       = 163;
-
-    const FETCH_IMMEDIATE       = 0;
-    const FETCH_BATCH           = 1;
-    const FETCH_OFFSET          = 3;
-    const FETCH_LAZY_OFFSET     = 4;
-    const FETCH_VHOLDER         = 1;
-    const FETCH_RECORD          = 2;
-    const FETCH_ARRAY           = 3;
-
-    const PORTABILITY_NONE          = 0;
-    const PORTABILITY_FIX_CASE      = 1;
-    const PORTABILITY_RTRIM         = 2;
-    const PORTABILITY_DELETE_COUNT  = 4;
-    const PORTABILITY_EMPTY_TO_NULL = 8;
-    const PORTABILITY_FIX_ASSOC_FIELD_NAMES = 16;
-    const PORTABILITY_EXPR          = 32;
-    const PORTABILITY_ALL           = 63;
-
-    const LOCK_OPTIMISTIC       = 0;
-    const LOCK_PESSIMISTIC      = 1;
-
-    const EXPORT_NONE               = 0;
-    const EXPORT_TABLES             = 1;
-    const EXPORT_CONSTRAINTS        = 2;
-    const EXPORT_ALL                = 7;
-
-    const HYDRATE_RECORD            = 2;
-    const HYDRATE_ARRAY             = 3;
-
-    const HYDRATE_NONE              = 4;
-    const IDENTIFIER_AUTOINC        = 1;
-    const IDENTIFIER_NATURAL        = 3;
-    const IDENTIFIER_COMPOSITE      = 4;
-    const MODEL_LOADING_AGGRESSIVE   = 1;
-    const MODEL_LOADING_CONSERVATIVE= 2;
-    
-    const BASE_CLASSES_DIRECTORY    = '_generated';
-
-    private function __construct() {}
-
-    public static function getTable($componentName)
-    {
-        return IPF_ORM_Manager::connection()->getTable($componentName);
-    }
-
-    public static function generateModelsFromYaml($directory, $extraAllwedReferences=array())
-    {
-        $files = array();
-        foreach ((array)$directory as $dir)
-            $files[] = $dir . '/models.yml';
-
-        // load schema
-        $import = new IPF_ORM_Import_Schema;
-        $definitions = $import->importSchema($files, $extraAllwedReferences);
-
-        // build
-        $models = array();
-        foreach ($definitions as $name => $definition) {
-            print "    $name\n";
-            $targetPath = substr($definition['input_file'], 0, -4);
-
-            $builder = new IPF_ORM_Import_Builder;
-            $builder->buildRecord($definition, $targetPath);
-
-            $models[] = $name;
-        }
-
-        return $models;
-    }
-
-    public static function createTablesFromModels($models)
-    {
-        IPF_ORM_Manager::connection()->export->exportClasses($models);
-    }
-
-    public static function generateSqlFromModels($app)
-    {
-        $sql = IPF_ORM_Manager::connection()->export->exportSortedClassesSql(IPF_Legacy_ORM_App::appModelList($app), false);
-
-        $build = '';
-        foreach ($sql as $query) {
-            $build .= $query.";\n\n";
-        }
-        return $build;
-    }
-
-    public static function dump($var, $output = true, $indent = "")
-    {
-        $ret = array();
-        switch (gettype($var)) {
-            case 'array':
-                $ret[] = 'Array(';
-                $indent .= "    ";
-                foreach ($var as $k => $v) {
-
-                    $ret[] = $indent . $k . ' : ' . self::dump($v, false, $indent);
-                }
-                $indent = substr($indent,0, -4);
-                $ret[] = $indent . ")";
-                break;
-            case 'object':
-                $ret[] = 'Object(' . get_class($var) . ')';
-                break;
-            default:
-                $ret[] = var_export($var, true);
-        }
-
-        if ($output) {
-            print implode("\n", $ret);
-        }
-
-        return implode("\n", $ret);
-    }
-
-    public static function GetObjectOr404($object, $id)
-    {
-        $obj = IPF_ORM::getTable($object)->findOneById($id);
-        if ($obj)
-            return $obj;
-        throw new IPF_HTTP_Error404();
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/access.php b/ipf/legacy_orm/orm/access.php
deleted file mode 100644 (file)
index d0a87d2..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-abstract class IPF_ORM_Access implements ArrayAccess
-{
-    public function setArray(array $array)
-    {
-        foreach ($array as $k => $v) {
-            $this->set($k, $v);
-        }
-
-        return $this;
-    }
-
-    public function __set($name, $value)
-    {
-        $this->set($name, $value);
-    }
-
-    public function __get($name)
-    {
-        return $this->get($name);
-    }
-
-    public function __isset($name)
-    {
-        return $this->contains($name);
-    }
-
-    public function __unset($name)
-    {
-        return $this->remove($name);
-    }
-
-    public function offsetExists($offset)
-    {
-        return $this->contains($offset);
-    }
-
-    public function offsetGet($offset)
-    {
-        return $this->get($offset);
-    }
-
-    public function offsetSet($offset, $value)
-    {
-        if ( ! isset($offset)) {
-            $this->add($value);
-        } else {
-            $this->set($offset, $value);
-        }
-    }
-
-    public function offsetUnset($offset)
-    {
-        return $this->remove($offset);
-    }
-
-    public function remove($offset)
-    {
-        throw new IPF_ORM_Exception('Remove is not supported for ' . get_class($this));
-    }
-
-    public function get($offset)
-    {
-        throw new IPF_ORM_Exception('Get is not supported for ' . get_class($this));
-    }
-
-    public function set($offset, $value)
-    {
-        throw new IPF_ORM_Exception('Set is not supported for ' . get_class($this));
-    }
-
-    public function contains($offset)
-    {
-        throw new IPF_ORM_Exception('Contains is not supported for ' . get_class($this));
-    }
-
-    public function add($value)
-    {
-        throw new IPF_ORM_Exception('Add is not supported for ' . get_class($this));
-    }
-}
diff --git a/ipf/legacy_orm/orm/adapter.php b/ipf/legacy_orm/orm/adapter.php
deleted file mode 100644 (file)
index 363cade..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-
-class IPF_ORM_Adapter
-{
-    const ATTR_AUTOCOMMIT = 0;
-    const ATTR_CASE = 8;
-    const ATTR_CLIENT_VERSION = 5;
-    const ATTR_CONNECTION_STATUS = 7;
-    const ATTR_CURSOR = 10;
-    const ATTR_CURSOR_NAME = 9;
-    const ATTR_ERRMODE = 3;
-    const ATTR_FETCH_CATALOG_NAMES = 15;
-    const ATTR_FETCH_TABLE_NAMES = 14;
-    const ATTR_MAX_COLUMN_LEN = 18;
-    const ATTR_ORACLE_NULLS = 11;
-    const ATTR_PERSISTENT = 12;
-    const ATTR_PREFETCH = 1;
-    const ATTR_SERVER_INFO = 6;
-    const ATTR_SERVER_VERSION = 4;
-    const ATTR_STATEMENT_CLASS = 13;
-    const ATTR_STRINGIFY_FETCHES = 17;
-    const ATTR_TIMEOUT = 2;
-    const CASE_LOWER = 2;
-    const CASE_NATURAL = 0;
-    const CASE_UPPER = 1;
-    const CURSOR_FWDONLY = 0;
-    const CURSOR_SCROLL = 1;
-    const ERR_ALREADY_EXISTS = NULL;
-    const ERR_CANT_MAP = NULL;
-    const ERR_CONSTRAINT = NULL;
-    const ERR_DISCONNECTED = NULL;
-    const ERR_MISMATCH = NULL;
-    const ERR_NO_PERM = NULL;
-    const ERR_NONE = '00000';
-    const ERR_NOT_FOUND = NULL;
-    const ERR_NOT_IMPLEMENTED = NULL;
-    const ERR_SYNTAX = NULL;
-    const ERR_TRUNCATED = NULL;
-    const ERRMODE_EXCEPTION = 2;
-    const ERRMODE_SILENT = 0;
-    const ERRMODE_WARNING = 1;
-    const FETCH_ASSOC = 2;
-    const FETCH_BOTH = 4;
-    const FETCH_BOUND = 6;
-    const FETCH_CLASS = 8;
-    const FETCH_CLASSTYPE = 262144;
-    const FETCH_COLUMN = 7;
-    const FETCH_FUNC = 10;
-    const FETCH_GROUP = 65536;
-    const FETCH_INTO = 9;
-    const FETCH_LAZY = 1;
-    const FETCH_NAMED = 11;
-    const FETCH_NUM = 3;
-    const FETCH_OBJ = 5;
-    const FETCH_ORI_ABS = 4;
-    const FETCH_ORI_FIRST = 2;
-    const FETCH_ORI_LAST = 3;
-    const FETCH_ORI_NEXT = 0;
-    const FETCH_ORI_PRIOR = 1;
-    const FETCH_ORI_REL = 5;
-    const FETCH_SERIALIZE = 524288;
-    const FETCH_UNIQUE = 196608;
-    const NULL_EMPTY_STRING = 1;
-    const NULL_NATURAL = 0;
-    const NULL_TO_STRING = NULL;
-    const PARAM_BOOL = 5;
-    const PARAM_INPUT_OUTPUT = -2147483648;
-    const PARAM_INT = 1;
-    const PARAM_LOB = 3;
-    const PARAM_NULL = 0;
-    const PARAM_STMT = 4;
-    const PARAM_STR = 2;
-}
diff --git a/ipf/legacy_orm/orm/cache/interface.php b/ipf/legacy_orm/orm/cache/interface.php
deleted file mode 100644 (file)
index f88a509..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-interface IPF_ORM_Cache_Interface 
-{
-    public function fetch($id, $testCacheValidity = true);
-    public function contains($id);
-    public function save($id, $data, $lifeTime = false);
-    public function delete($id);
-}
diff --git a/ipf/legacy_orm/orm/collection.php b/ipf/legacy_orm/orm/collection.php
deleted file mode 100644 (file)
index 9bf5e52..0000000
+++ /dev/null
@@ -1,556 +0,0 @@
-<?php
-
-class IPF_ORM_Collection extends IPF_ORM_Access implements Countable, IteratorAggregate, Serializable
-{
-    protected $data = array();
-    protected $_table;
-    protected $_snapshot = array();
-    protected $reference;
-    protected $referenceField;
-    protected $relation;
-    protected $keyColumn;
-
-    public function __construct($table, $keyColumn = null)
-    {
-        if ( ! ($table instanceof IPF_ORM_Table)) {
-            $table = IPF_ORM::getTable($table);
-        }
-
-        $this->_table = $table;
-
-        if ($keyColumn === null) {
-            $keyColumn = $table->getBoundQueryPart('indexBy');
-        }
-
-        if ($keyColumn === null) {
-               $keyColumn = $table->getAttribute(IPF_ORM::ATTR_COLL_KEY);
-        }
-
-        if ($keyColumn !== null) {
-            $this->keyColumn = $keyColumn;
-        }
-    }
-
-    public function getTable()
-    {
-        return $this->_table;
-    }
-
-    public function setData(array $data) 
-    {
-        $this->data = $data;
-    }
-
-    public function serialize()
-    {
-        $vars = get_object_vars($this);
-
-        unset($vars['reference']);
-        unset($vars['reference_field']);
-        unset($vars['relation']);
-        unset($vars['expandable']);
-        unset($vars['expanded']);
-        unset($vars['generator']);
-
-        $vars['_table'] = $vars['_table']->getComponentName();
-
-        return serialize($vars);
-    }
-
-    public function unserialize($serialized)
-    {
-        $connection = IPF_ORM_Manager::connection();
-
-        $array = unserialize($serialized);
-
-        foreach ($array as $name => $values) {
-            $this->$name = $values;
-        }
-
-        $this->_table = $connection->getTable($this->_table);
-
-        $keyColumn = isset($array['keyColumn']) ? $array['keyColumn'] : null;
-        if ($keyColumn === null) {
-            $keyColumn = $this->_table->getBoundQueryPart('indexBy');
-        }
-
-        if ($keyColumn !== null) {
-            $this->keyColumn = $keyColumn;
-        }
-    }
-
-    public function setKeyColumn($column)
-    {
-        $this->keyColumn = $column;
-        
-        return $this;
-    }
-
-    public function getKeyColumn()
-    {
-        return $this->keyColumn;
-    }
-
-    public function getData()
-    {
-        return $this->data;
-    }
-
-    public function getFirst()
-    {
-        return reset($this->data);
-    }
-
-    public function getLast()
-    {
-        return end($this->data);
-    }
-
-    public function end()
-    {
-        return end($this->data);
-    }
-
-    public function key()
-    {
-        return key($this->data);
-    }
-
-    public function setReference(IPF_ORM_Record $record, IPF_ORM_Relation $relation)
-    {
-        $this->reference = $record;
-        $this->relation  = $relation;
-
-        if ($relation instanceof IPF_ORM_Relation_ForeignKey || 
-            $relation instanceof IPF_ORM_Relation_LocalKey) {
-
-            $this->referenceField = $relation->getForeignFieldName();
-
-            $value = $record->get($relation->getLocalFieldName());
-
-            foreach ($this->data as $record) {
-                if ($value !== null) {
-                    $record->set($this->referenceField, $value, false);
-                } else {
-                    $record->set($this->referenceField, $this->reference, false);
-                }
-            }
-        } elseif ($relation instanceof IPF_ORM_Relation_Association) {
-
-        }
-    }
-
-    public function getReference()
-    {
-        return $this->reference;
-    }
-
-    public function remove($key)
-    {
-        $removed = $this->data[$key];
-
-        unset($this->data[$key]);
-        return $removed;
-    }
-
-    public function contains($key)
-    {
-        return isset($this->data[$key]);
-    }
-
-    public function search(IPF_ORM_Record $record)
-    {
-        return array_search($record, $this->data, true);
-    }
-
-    public function get($key)
-    {
-        if ( ! isset($this->data[$key])) {
-            $record = $this->_table->create();
-
-            if (isset($this->referenceField)) {
-                $value = $this->reference->get($this->relation->getLocalFieldName());
-
-                if ($value !== null) {
-                    $record->set($this->referenceField, $value, false);
-                } else {
-                    $record->set($this->referenceField, $this->reference, false);
-                }
-            }
-            if ($key === null) {
-                $this->data[] = $record;
-            } else {
-                $this->data[$key] = $record;
-            }
-
-            if (isset($this->keyColumn)) {
-                $record->set($this->keyColumn, $key);
-            }
-
-            return $record;
-        }
-
-        return $this->data[$key];
-    }
-
-    public function getPrimaryKeys()
-    {
-        $list = array();
-        $name = $this->_table->getIdentifier();
-
-        foreach ($this->data as $record) {
-            if (is_array($record) && isset($record[$name])) {
-                $list[] = $record[$name];
-            } else {
-                $list[] = $record->getIncremented();
-            }
-        }
-        return $list;
-    }
-
-    public function getKeys()
-    {
-        return array_keys($this->data);
-    }
-
-    public function count()
-    {
-        return count($this->data);
-    }
-
-    public function set($key, $record)
-    {
-        if (isset($this->referenceField)) {
-            $record->set($this->referenceField, $this->reference, false);
-        }
-
-        $this->data[$key] = $record;
-    }
-
-    public function add($record, $key = null)
-    {
-        if (isset($this->referenceField)) {
-            $value = $this->reference->get($this->relation->getLocalFieldName());
-
-            if ($value !== null) {
-                $record->set($this->referenceField, $value, false);
-            } else {
-                $record->set($this->referenceField, $this->reference, false);
-            }
-        }
-        /**
-         * for some weird reason in_array cannot be used here (php bug ?)
-         *
-         * if used it results in fatal error : [ nesting level too deep ]
-         */
-        foreach ($this->data as $val) {
-            if ($val === $record) {
-                return false;
-            }
-        }
-
-        if (isset($key)) {
-            if (isset($this->data[$key])) {
-                return false;
-            }
-            $this->data[$key] = $record;
-            return true;
-        }
-
-        if (isset($this->keyColumn)) {
-            $value = $record->get($this->keyColumn);
-            if ($value === null) {
-                throw new IPF_ORM_Exception("Couldn't create collection index. Record field '".$this->keyColumn."' was null.");
-            }
-            $this->data[$value] = $record;
-        } else {
-            $this->data[] = $record;
-        }
-
-        return true;
-    }
-    
-    public function merge(IPF_ORM_Collection $coll)
-    {
-        $localBase = $this->getTable()->getComponentName();
-        $otherBase = $coll->getTable()->getComponentName();
-        
-        if ($otherBase != $localBase && !is_subclass_of($otherBase, $localBase) ) {
-            throw new IPF_ORM_Exception("Can't merge collections with incompatible record types");
-        }
-        
-        foreach ($coll->getData() as $record) {
-            $this->add($record);
-        }
-        
-        return $this;
-    }
-
-    public function loadRelated($name = null)
-    {
-        $list = array();
-        $query   = new IPF_ORM_Query($this->_table->getConnection());
-
-        if ( ! isset($name)) {
-            foreach ($this->data as $record) {
-                $value = $record->getIncremented();
-                if ($value !== null) {
-                    $list[] = $value;
-                }
-            }
-            $query->from($this->_table->getComponentName());
-            $query->where($this->_table->getComponentName() . '.id IN (' . substr(str_repeat("?, ", count($list)),0,-2) . ')');
-
-            return $query;
-        }
-
-        $rel     = $this->_table->getRelation($name);
-
-        if ($rel instanceof IPF_ORM_Relation_LocalKey || $rel instanceof IPF_ORM_Relation_ForeignKey) {
-            foreach ($this->data as $record) {
-                $list[] = $record[$rel->getLocal()];
-            }
-        } else {
-            foreach ($this->data as $record) {
-                $value = $record->getIncremented();
-                if ($value !== null) {
-                    $list[] = $value;
-                }
-            }
-        }
-
-        $dql     = $rel->getRelationDql(count($list), 'collection');
-
-        $coll    = $query->query($dql, $list);
-
-        $this->populateRelated($name, $coll);
-    }
-
-    public function populateRelated($name, IPF_ORM_Collection $coll)
-    {
-        $rel     = $this->_table->getRelation($name);
-        $table   = $rel->getTable();
-        $foreign = $rel->getForeign();
-        $local   = $rel->getLocal();
-
-        if ($rel instanceof IPF_ORM_Relation_LocalKey) {
-            foreach ($this->data as $key => $record) {
-                foreach ($coll as $k => $related) {
-                    if ($related[$foreign] == $record[$local]) {
-                        $this->data[$key]->setRelated($name, $related);
-                    }
-                }
-            }
-        } elseif ($rel instanceof IPF_ORM_Relation_ForeignKey) {
-            foreach ($this->data as $key => $record) {
-                if ( ! $record->exists()) {
-                    continue;
-                }
-                $sub = new IPF_ORM_Collection($table);
-
-                foreach ($coll as $k => $related) {
-                    if ($related[$foreign] == $record[$local]) {
-                        $sub->add($related);
-                        $coll->remove($k);
-                    }
-                }
-
-                $this->data[$key]->setRelated($name, $sub);
-            }
-        } elseif ($rel instanceof IPF_ORM_Relation_Association) {
-            $identifier = $this->_table->getIdentifier();
-            $asf        = $rel->getAssociationFactory();
-            $name       = $table->getComponentName();
-
-            foreach ($this->data as $key => $record) {
-                if ( ! $record->exists()) {
-                    continue;
-                }
-                $sub = new IPF_ORM_Collection($table);
-                foreach ($coll as $k => $related) {
-                    if ($related->get($local) == $record[$identifier]) {
-                        $sub->add($related->get($name));
-                    }
-                }
-                $this->data[$key]->setRelated($name, $sub);
-
-            }
-        }
-    }
-
-    public function getNormalIterator()
-    {
-        return new IPF_ORM_Collection_Iterator_Normal($this);
-    }
-
-    public function takeSnapshot()
-    {
-        $this->_snapshot = $this->data;
-        
-        return $this;
-    }
-
-    public function getSnapshot()
-    {
-        return $this->_snapshot;
-    }
-
-    public function processDiff() 
-    {
-        foreach (array_udiff($this->_snapshot, $this->data, array($this, "compareRecords")) as $record) {
-            $record->delete();
-        }
-
-        return $this;
-    }
-
-    public function toArray($deep = false, $prefixKey = false)
-    {
-        $data = array();
-        foreach ($this as $key => $record) {
-            
-            $key = $prefixKey ? get_class($record) . '_' .$key:$key;
-            
-            $data[$key] = $record->toArray($deep, $prefixKey);
-        }
-        
-        return $data;
-    }
-
-    public function fromArray($array, $deep = true)
-    {
-        $data = array();
-        foreach ($array as $rowKey => $row) {
-            $this[$rowKey]->fromArray($row, $deep);
-        }
-    }
-
-    public function synchronizeWithArray(array $array)
-    {
-        foreach ($this as $key => $record) {
-            if (isset($array[$key])) {
-                $record->synchronizeWithArray($array[$key]);
-                unset($array[$key]);
-            } else {
-                // remove records that don't exist in the array
-                $this->remove($key);
-            }
-        }
-        // create new records for each new row in the array
-        foreach ($array as $rowKey => $row) {
-            $this[$rowKey]->fromArray($row);
-        }
-    }
-    public function synchronizeFromArray(array $array)
-    {
-        return $this->synchronizeWithArray($array);
-    }
-
-    public function getDeleteDiff()
-    {
-        return array_udiff($this->_snapshot, $this->data, array($this, 'compareRecords'));
-    }
-
-    public function getInsertDiff()
-    {
-        return array_udiff($this->data, $this->_snapshot, array($this, "compareRecords"));
-    }
-
-    protected function compareRecords($a, $b)
-    {
-        if ($a->getOid() == $b->getOid()) {
-            return 0;
-        }
-        
-        return ($a->getOid() > $b->getOid()) ? 1 : -1;
-    }
-
-    public function save(IPF_ORM_Connection $conn = null)
-    {
-        if ($conn == null) {
-            $conn = $this->_table->getConnection();
-        }
-        
-        $conn->beginInternalTransaction();
-
-        $conn->transaction->addCollection($this);
-
-        $this->processDiff();
-
-        foreach ($this->getData() as $key => $record) {
-            $record->save($conn);
-        }
-
-        $conn->commit();
-
-        return $this;
-    }
-
-    public function delete(IPF_ORM_Connection $conn = null, $clearColl = true)
-    {
-        if ($conn == null) {
-            $conn = $this->_table->getConnection();
-        }
-
-        $conn->beginInternalTransaction();
-        $conn->transaction->addCollection($this);
-
-        foreach ($this as $key => $record) {
-            $record->delete($conn);
-        }
-
-        $conn->commit();
-        
-        if ($clearColl) {
-            $this->clear();
-        }
-        
-        return $this;
-    }
-    
-    public function clear()
-    {
-        $this->data = array();
-    }
-
-    public function free($deep = false)
-    {
-        foreach ($this->getData() as $key => $record) {
-            if ( ! ($record instanceof IPF_ORM_Null)) {
-                $record->free($deep);
-            }
-        }
-
-        $this->data = array();
-
-        if ($this->reference) {
-            $this->reference->free($deep);
-            $this->reference = null;
-        }
-    }
-
-    public function getIterator()
-    {
-        $data = $this->data;
-        return new ArrayIterator($data);
-    }
-
-    public function __toString()
-    {
-        return IPF_ORM_Utils::getCollectionAsString($this);
-    }
-    
-    public function getRelation()
-    {
-        return $this->relation;
-    }
-
-    public function __debugInfo()
-    {
-        $r = array();
-        foreach ($this->data as $item)
-            $r[] = $item;
-        return $r;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/configurable.php b/ipf/legacy_orm/orm/configurable.php
deleted file mode 100644 (file)
index a19ad8f..0000000
+++ /dev/null
@@ -1,180 +0,0 @@
-<?php
-
-abstract class IPF_ORM_Configurable
-{
-    protected $attributes = array();
-    protected $parent;
-    protected $_params = array();
-
-    public function getAttributeFromString($stringAttributeName)
-    {
-      if (is_string($stringAttributeName)) {
-          $upper = strtoupper($stringAttributeName);
-          $const = 'IPF_ORM::ATTR_' . $upper; 
-          if (defined($const)) {
-              return constant($const);
-          } else {
-              throw new IPF_ORM_Exception('Unknown attribute: "' . $stringAttributeName . '"');
-          }
-      } else {
-        return false;
-      }
-    }
-
-    public function getAttributeValueFromString($stringAttributeName, $stringAttributeValueName)
-    {
-        $const = 'IPF_ORM::' . strtoupper($stringAttributeName) . '_' . strtoupper($stringAttributeValueName);
-        if (defined($const)) {
-            return constant($const);
-        } else {
-            throw new IPF_ORM_Exception('Unknown attribute value: "' . $value . '"');
-        }
-    }
-
-    public function setAttribute($attribute, $value)
-    {
-        if (is_string($attribute)) {
-            $stringAttribute = $attribute;
-            $attribute = $this->getAttributeFromString($attribute);
-            $this->_state = $attribute;
-        }
-
-        if (is_string($value) && isset($stringAttribute)) {
-            $value = $this->getAttributeValueFromString($stringAttribute, $value);
-        }
-
-        switch ($attribute) {
-            case IPF_ORM::ATTR_COLL_KEY:
-                if ( ! ($this instanceof IPF_ORM_Table)) {
-                    throw new IPF_ORM_Exception("This attribute can only be set at table level.");
-                }
-                if ($value !== null && ! $this->hasField($value)) {
-                    throw new IPF_ORM_Exception("Couldn't set collection key attribute. No such field '$value'.");
-                }
-                break;
-            case IPF_ORM::ATTR_CACHE:
-            case IPF_ORM::ATTR_RESULT_CACHE:
-            case IPF_ORM::ATTR_QUERY_CACHE:
-                if ($value !== null) {
-                    if ( ! ($value instanceof IPF_ORM_Cache_Interface)) {
-                        throw new IPF_ORM_Exception('Cache driver should implement IPF_ORM_Cache_Interface');
-                    }
-                }
-                break;
-            case IPF_ORM::ATTR_PORTABILITY:
-            case IPF_ORM::ATTR_DEFAULT_TABLE_TYPE:
-            case IPF_ORM::ATTR_EMULATE_DATABASE:
-            case IPF_ORM::ATTR_USE_NATIVE_ENUM:
-            case IPF_ORM::ATTR_EXPORT:
-            case IPF_ORM::ATTR_DECIMAL_PLACES:
-            case IPF_ORM::ATTR_LOAD_REFERENCES:
-            case IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE:
-            case IPF_ORM::ATTR_MODEL_LOADING:
-            case IPF_ORM::ATTR_RESULT_CACHE_LIFESPAN:
-            case IPF_ORM::ATTR_QUERY_CACHE_LIFESPAN:
-            case IPF_ORM::ATTR_RECURSIVE_MERGE_FIXTURES;
-            case IPF_ORM::ATTR_SINGULARIZE_IMPORT;
-
-                break;
-            case IPF_ORM::ATTR_FIELD_CASE:
-                if ($value != 0 && $value != CASE_LOWER && $value != CASE_UPPER)
-                    throw new IPF_ORM_Exception('Field case attribute should be either 0, CASE_LOWER or CASE_UPPER constant.');
-                break;
-            default:
-                throw new IPF_ORM_Exception("Unknown attribute.");
-        }
-
-        $this->attributes[$attribute] = $value;
-    }
-
-    public function getParams($namespace = null)
-    {
-        if ($namespace == null) {
-            $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE);
-        }
-
-        if (!isset($this->_params[$namespace])) {
-            return null;
-        }
-
-        return $this->_params[$namespace];
-    }
-    
-    public function getParamNamespaces()
-    {
-        return array_keys($this->_params);
-    }
-
-    public function setParam($name, $value, $namespace = null) 
-    {
-        if ($namespace == null) {
-            $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE);
-        }
-
-        $this->_params[$namespace][$name] = $value;
-
-        return $this;
-    }
-    
-    public function getParam($name, $namespace = null) 
-    {
-        if ($namespace == null) {
-            $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE);
-        }
-
-        if (!isset($this->_params[$name])) {
-            if (isset($this->parent)) {
-                return $this->parent->getParam($name, $namespace);
-            }
-            return null;
-        }
-        
-        return $this->_params[$namespace][$name];
-    }
-
-    public function getAttribute($attribute)
-    {
-        if (is_string($attribute)) {
-            $upper = strtoupper($attribute);
-
-            $const = 'IPF_ORM::ATTR_' . $upper; 
-
-            if (defined($const)) {
-                $attribute = constant($const);
-                $this->_state = $attribute;
-            } else {
-                throw new IPF_ORM_Exception('Unknown attribute: "' . $attribute . '"');
-            }
-        }
-
-        $attribute = (int) $attribute;
-
-        if ($attribute < 0) {
-            throw new IPF_ORM_Exception('Unknown attribute.');
-        }
-
-        if (isset($this->attributes[$attribute])) {
-            return $this->attributes[$attribute];
-        }
-        
-        if (isset($this->parent)) {
-            return $this->parent->getAttribute($attribute);
-        }
-        return null;
-    }
-
-    public function getAttributes()
-    {
-        return $this->attributes;
-    }
-
-    public function setParent(IPF_ORM_Configurable $component)
-    {
-        $this->parent = $component;
-    }
-
-    public function getParent()
-    {
-        return $this->parent;
-    }
-}
diff --git a/ipf/legacy_orm/orm/connection.php b/ipf/legacy_orm/orm/connection.php
deleted file mode 100644 (file)
index 7fddf02..0000000
+++ /dev/null
@@ -1,646 +0,0 @@
-<?php
-
-abstract class IPF_ORM_Connection extends IPF_ORM_Configurable implements Countable, IteratorAggregate
-{
-    protected $dbh;
-    protected $tables           = array();
-    protected $_name = 0;
-    protected $driverName;
-    protected $pendingAttributes  = array();
-
-    private $modules = array('transaction' => false,
-                             'expression'  => false,
-                             'export'      => false,
-                             'unitOfWork'  => false,
-                             );
-
-    protected $properties = array('sql_comments'        => array(array('start' => '--', 'end' => "\n", 'escape' => false),
-                                                                 array('start' => '/*', 'end' => '*/', 'escape' => false)),
-                                  'identifier_quoting'  => '"',
-                                  'string_quoting'      => array('start' => "'",
-                                                                 'end' => "'",
-                                                                 'escape' => false,
-                                                                 'escape_pattern' => false),
-                                  'wildcards'           => array('%', '_'),
-                                  'varchar_max_length'  => 255,
-                                  );
-
-    protected $options    = array();
-    private static $availableDrivers    = array(
-                                        'Mysql',
-                                        'Pgsql',
-                                        'Oracle',
-                                        'Informix',
-                                        'Mssql',
-                                        'Sqlite',
-                                        'Firebird'
-                                        );
-    protected $_count = 0;
-
-    public $dbListeners = array();
-
-    public function __construct(IPF_ORM_Manager $manager, $pdo, $user = null, $pass = null)
-    {
-        $this->dbh = $pdo;
-
-        $this->setParent($manager);
-        $this->dbListeners = $manager->dbListeners;
-
-        $this->setAttribute(IPF_ORM::ATTR_CASE, IPF_ORM::CASE_NATURAL);
-        $this->setAttribute(IPF_ORM::ATTR_ERRMODE, IPF_ORM::ERRMODE_EXCEPTION);
-
-        $this->notifyDBListeners('onOpen', $this);
-    }
-
-    public function getOptions()
-    {
-      return $this->options;
-    }
-
-    public function getOption($option)
-    {
-        if (isset($this->options[$option])) {
-            return $this->options[$option];
-        }
-    }
-
-    public function setOption($option, $value)
-    {
-      return $this->options[$option] = $value;
-    }
-
-    public function getAttribute($attribute)
-    {
-        if (is_string($attribute)) {
-            $stringAttribute = $attribute;
-            $attribute = $this->getAttributeFromString($attribute);
-        }
-
-        if ($attribute >= 100) {
-            if ( ! isset($this->attributes[$attribute])) {
-                return parent::getAttribute($attribute);
-            }
-            return $this->attributes[$attribute];
-        }
-
-        try {
-            return $this->dbh->getAttribute($attribute);
-        } catch (Exception $e) {
-            throw new IPF_ORM_Exception('Attribute ' . $attribute . ' not found.');
-        }
-    }
-
-    public static function getAvailableDrivers()
-    {
-        return PDO::getAvailableDrivers();
-    }
-
-    public function setAttribute($attribute, $value)
-    {
-        if (is_string($attribute)) {
-            $attributeString = $attribute;
-            $attribute = parent::getAttributeFromString($attribute);
-        }
-
-        if (is_string($value) && isset($attributeString)) {
-            $value = parent::getAttributeValueFromString($attributeString, $value);
-        }
-
-        if ($attribute >= 100) {
-            parent::setAttribute($attribute, $value);
-        } else {
-            $this->dbh->setAttribute($attribute, $value);
-        }
-
-        return $this;
-    }
-
-    public function getName()
-    {
-        return $this->_name;
-    }
-
-    public function getDriverName()
-    {
-        return $this->driverName;
-    }
-
-    public function __get($name)
-    {
-        if (isset($this->properties[$name])) {
-            return $this->properties[$name];
-        }
-
-        if ( ! isset($this->modules[$name])) {
-            throw new IPF_ORM_Exception('Unknown module / property ' . $name);
-        }
-        if ($this->modules[$name] === false) {
-            switch ($name) {
-                case 'unitOfWork':
-                    $this->modules[$name] = new IPF_ORM_Connection_UnitOfWork($this);
-                    break;
-                default:
-                    $class = 'IPF_ORM_' . ucwords($name) . '_' . $this->getDriverName();
-                    $this->modules[$name] = new $class($this);
-                }
-        }
-
-        return $this->modules[$name];
-    }
-
-    public function getManager()
-    {
-        return $this->getParent();
-    }
-
-    public function getDbh()
-    {
-        return $this->dbh;
-    }
-
-    public function incrementQueryCount() 
-    {
-        $this->_count++;
-    }
-
-    public function replace(IPF_ORM_Table $table, array $fields, array $keys)
-    {
-        if (empty($keys)) {
-            throw new IPF_ORM_Exception('Not specified which fields are keys');
-        }
-        $identifier = (array) $table->getIdentifier();
-        $condition = array();
-
-        foreach ($fields as $fieldName => $value) {
-            if (in_array($fieldName, $keys)) {
-                if ($value !== null) {
-                    $condition[] = $table->getColumnName($fieldName) . ' = ?';
-                    $conditionValues[] = $value;
-                }
-            }
-        }
-
-        $affectedRows = 0;
-        if ( ! empty($condition) && ! empty($conditionValues)) {
-            $query = 'DELETE FROM ' . $this->quoteIdentifier($table->getTableName())
-                    . ' WHERE ' . implode(' AND ', $condition);
-
-            $affectedRows = $this->exec($query, $conditionValues);
-        }
-
-        $this->insert($table, $fields);
-
-        $affectedRows++;
-
-        return $affectedRows;
-    }
-
-    public function delete(IPF_ORM_Table $table, array $identifier)
-    {
-        $tmp = array();
-
-        foreach (array_keys($identifier) as $id) {
-            $tmp[] = $this->quoteIdentifier($table->getColumnName($id)) . ' = ?';
-        }
-
-        $query = 'DELETE FROM '
-               . $this->quoteIdentifier($table->getTableName())
-               . ' WHERE ' . implode(' AND ', $tmp);
-        
-        return $this->exec($query, array_values($identifier));
-    }
-
-    public function update(IPF_ORM_Table $table, array $fields, array $identifier)
-    {
-        if (empty($fields)) {
-            return false;
-        }
-
-        $set = array();
-        foreach ($fields as $fieldName => $value) {
-            if ($value instanceof IPF_ORM_Expression) {
-                $set[] = $this->quoteIdentifier($table->getColumnName($fieldName)) . ' = ' . $value->getSql();
-                unset($fields[$fieldName]);
-            } else {
-                $set[] = $this->quoteIdentifier($table->getColumnName($fieldName)) . ' = ?';
-            }
-        }
-
-        $params = array_merge(array_values($fields), array_values($identifier));
-
-        $sql  = 'UPDATE ' . $this->quoteIdentifier($table->getTableName())
-              . ' SET ' . implode(', ', $set)
-              . ' WHERE ' . implode(' = ? AND ', $table->getIdentifierColumnNames())
-              . ' = ?';
-          
-        return $this->exec($sql, $params);
-    }
-
-    public function insert(IPF_ORM_Table $table, array $fields)
-    {
-        $tableName = $table->getTableName();
-
-        // column names are specified as array keys
-        $cols = array();
-        // the query VALUES will contain either expresions (eg 'NOW()') or ?
-        $a = array();
-        foreach ($fields as $fieldName => $value) {
-            $cols[] = $this->quoteIdentifier($table->getColumnName($fieldName));
-            if ($value instanceof IPF_ORM_Expression) {
-                $a[] = $value->getSql();
-                unset($fields[$fieldName]);
-            } else {
-                $a[] = '?';
-            }
-        }
-
-        // build the statement
-        $query = 'INSERT INTO ' . $this->quoteIdentifier($tableName)
-                . ' (' . implode(', ', $cols) . ')'
-                . ' VALUES (' . implode(', ', $a) . ')';
-
-        return $this->exec($query, array_values($fields));
-    }
-
-    public abstract function quoteIdentifier($str);
-
-    public function convertBooleans($item)
-    {
-        if (is_array($item)) {
-            foreach ($item as $k => $value) {
-                if (is_bool($value)) {
-                    $item[$k] = (int) $value;
-                }
-            }
-        } else {
-            if (is_bool($item)) {
-                $item = (int) $item;
-            }
-        }
-        return $item;
-    }
-
-    public function quote($input, $type=null)
-    {
-        if ($type === null)
-            $type = gettype($input);
-
-        switch ($type) {
-            case 'integer':
-            case 'enum':
-            case 'boolean':
-            case 'double':
-            case 'float':
-            case 'bool':
-            case 'decimal':
-            case 'int':
-                return $input;
-            case 'array':
-            case 'object':
-                $input = serialize($input);
-            case 'date':
-            case 'time':
-            case 'timestamp':
-            case 'string':
-            case 'char':
-            case 'varchar':
-            case 'text':
-            case 'gzip':
-            case 'blob':
-            case 'clob':
-                return $this->getDbh()->quote($input);
-            default:
-                throw new IPF_ORM_Exception('Unsupported type \''.$type.'\'.');
-        }
-    }
-
-    public function escapePattern($text)
-    {
-        $q = $this->string_quoting['escape_pattern'];
-        $text = str_replace($q, $q . $q, $text);
-        foreach ($this->wildcards as $wildcard) {
-            $text = str_replace($wildcard, $q . $wildcard, $text);
-        }
-        return $text;
-    }
-
-    public function setDateFormat($format = null)
-    {
-    }
-
-    public function fetchAll($statement, array $params = array()) 
-    {
-        return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_ASSOC);
-    }
-
-    public function fetchOne($statement, array $params = array(), $colnum = 0) 
-    {
-        return $this->execute($statement, $params)->fetchColumn($colnum);
-    }
-
-    public function fetchRow($statement, array $params = array()) 
-    {
-        return $this->execute($statement, $params)->fetch(IPF_ORM::FETCH_ASSOC);
-    }
-
-    public function fetchArray($statement, array $params = array()) 
-    {
-        return $this->execute($statement, $params)->fetch(IPF_ORM::FETCH_NUM);
-    }
-
-    public function fetchColumn($statement, array $params = array(), $colnum = 0) 
-    {
-        return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_COLUMN, $colnum);
-    }
-
-    public function fetchAssoc($statement, array $params = array()) 
-    {
-        return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_ASSOC);
-    }
-
-    public function fetchBoth($statement, array $params = array()) 
-    {
-        return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_BOTH);
-    }
-
-    public function query($query, array $params = array(), $hydrationMode = null)
-    {
-        $parser = new IPF_ORM_Query($this);
-        $res = $parser->query($query, $params, $hydrationMode);
-        $parser->free();
-
-        return $res;
-    }
-
-    public function prepare($statement)
-    {
-        try {
-            $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_PREPARE, $statement);
-
-            $this->notifyDBListeners('prePrepare', $event);
-
-            $stmt = false;
-
-            if ( ! $event->skipOperation) {
-                $stmt = $this->dbh->prepare($statement);
-            }
-
-            $this->notifyDBListeners('postPrepare', $event);
-
-            return new IPF_ORM_Connection_Statement($this, $stmt);
-        } catch (IPF_ORM_Exception_Adapter $e) {
-        } catch (PDOException $e) {
-        }
-
-        $this->rethrowException($e, $this);
-    }
-
-    public function queryOne($query, array $params = array()) 
-    {
-        $parser = new IPF_ORM_Query($this);
-
-        $coll = $parser->query($query, $params);
-        if ( ! $coll->contains(0)) {
-            return false;
-        }
-        return $coll[0];
-    }
-
-    public function select($query, $limit = 0, $offset = 0)
-    {
-        if ($limit > 0 || $offset > 0) {
-            $query = $this->modifyLimitQuery($query, $limit, $offset);
-        }
-        return $this->execute($query);
-    }
-
-    public function standaloneQuery($query, $params = array())
-    {
-        return $this->execute($query, $params);
-    }
-
-    public function execute($query, array $params = array())
-    {
-        try {
-            if ( ! empty($params)) {
-                $stmt = $this->prepare($query);
-                $stmt->execute($params);
-
-                return $stmt;
-            } else {
-                $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_QUERY, $query, $params);
-
-                $this->notifyDBListeners('preQuery', $event);
-
-                if ( ! $event->skipOperation) {
-                    $stmt = $this->dbh->query($query);
-                    $this->_count++;
-                }
-                $this->notifyDBListeners('postQuery', $event);
-
-                return $stmt;
-            }
-        } catch (IPF_ORM_Exception_Adapter $e) {
-        } catch (PDOException $e) {
-        }
-
-        $this->rethrowException($e, $this);
-    }
-
-    public function exec($query, array $params = array())
-    {
-        try {
-            if ( ! empty($params)) {
-                $stmt = $this->prepare($query);
-                $stmt->execute($params);
-
-                return $stmt->rowCount();
-            } else {
-                $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_EXEC, $query, $params);
-
-                $this->notifyDBListeners('preExec', $event);
-                if ( ! $event->skipOperation) {
-                    $count = $this->dbh->exec($query);
-
-                    $this->_count++;
-                }
-                $this->notifyDBListeners('postExec', $event);
-
-                return $count;
-            }
-        } catch (IPF_ORM_Exception_Adapter $e) {
-        } catch (PDOException $e) { }
-
-        $this->rethrowException($e, $this);
-    }
-
-    public function rethrowException(Exception $e, $invoker)
-    {
-        $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_ERROR);
-        $this->notifyDBListeners('onError', $event);
-
-        $name = 'IPF_ORM_Exception_' . $this->driverName;
-        $exc  = new $name($e->getMessage(), (int) $e->getCode());
-        if (!is_array($e->errorInfo)) {
-            $e->errorInfo = array(null, null, null, null);
-        }
-        $exc->processErrorInfo($e->errorInfo);
-        throw $exc;
-    }
-
-    public function hasTable($name)
-    {
-        return isset($this->tables[$name]);
-    }
-
-    public function getTable($name)
-    {
-        if (!isset($this->tables[$name]))
-            $this->tables[$name] = new IPF_ORM_Table($name, $this);
-        return $this->tables[$name];
-    }
-
-    public function getTables()
-    {
-        return $this->tables;
-    }
-
-    public function getIterator()
-    {
-        return new ArrayIterator($this->tables);
-    }
-
-    public function count()
-    {
-        return $this->_count;
-    }
-
-    public function addTable(IPF_ORM_Table $table)
-    {
-        $name = $table->getComponentName();
-
-        if (isset($this->tables[$name])) {
-            return false;
-        }
-        $this->tables[$name] = $table;
-        return true;
-    }
-
-    public function create($name)
-    {
-        return $this->getTable($name)->create();
-    }
-    
-    public function createQuery()
-    {
-        return new IPF_ORM_Query($this);
-    }
-
-    public function flush()
-    {
-        $this->beginInternalTransaction();
-        $this->unitOfWork->saveAll();
-        $this->commit();
-    }
-
-    public function clear()
-    {
-        foreach ($this->tables as $k => $table) {
-            $table->getRepository()->evictAll();
-            $table->clear();
-        }
-    }
-
-    public function evictTables()
-    {
-        $this->tables = array();
-        $this->exported = array();
-    }
-
-    public function getTransactionLevel()
-    {
-        return $this->transaction->getTransactionLevel();
-    }
-
-    public function errorCode()
-    {
-        return $this->dbh->errorCode();
-    }
-
-    public function errorInfo()
-    {
-        return $this->dbh->errorInfo();
-    }
-    
-    public function getCacheDriver()
-    {
-        return $this->getResultCacheDriver();
-    }
-    
-    public function getResultCacheDriver()
-    {
-        if ( ! $this->getAttribute(IPF_ORM::ATTR_RESULT_CACHE)) {
-            throw new IPF_ORM_Exception('Result Cache driver not initialized.');
-        }
-
-        return $this->getAttribute(IPF_ORM::ATTR_RESULT_CACHE);
-    }
-    
-    public function getQueryCacheDriver()
-    {
-        if ( ! $this->getAttribute(IPF_ORM::ATTR_QUERY_CACHE)) {
-            throw new IPF_ORM_Exception('Query Cache driver not initialized.');
-        }
-
-        return $this->getAttribute(IPF_ORM::ATTR_QUERY_CACHE);
-    }
-
-    public function lastInsertId()
-    {
-        return $this->getDbh()->lastInsertId();
-    }
-
-    public function beginTransaction($savepoint = null)
-    {
-        return $this->transaction->beginTransaction($savepoint);
-    }
-    
-    public function beginInternalTransaction($savepoint = null)
-    {
-        return $this->transaction->beginInternalTransaction($savepoint);
-    }
-
-    public function commit($savepoint = null)
-    {
-        return $this->transaction->commit($savepoint);
-    }
-
-    public function rollback($savepoint = null)
-    {
-        $this->transaction->rollback($savepoint);
-    }
-
-    public function modifyLimitQuery($query, $limit = false, $offset = false, $isManip = false)
-    {
-        return $query;
-    }
-    
-    public function modifyLimitSubquery(IPF_ORM_Table $rootTable, $query, $limit = false,
-            $offset = false, $isManip = false)
-    {
-        return $this->modifyLimitQuery($query, $limit, $offset, $isManip);
-    }
-
-    public function __toString()
-    {
-        return IPF_ORM_Utils::getConnectionAsString($this);
-    }
-
-    public function notifyDBListeners($method, $event)
-    {
-        foreach ($this->dbListeners as $listener)
-            if (is_callable(array($listener, $method)))
-                $listener->$method($event);
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/connection/module.php b/ipf/legacy_orm/orm/connection/module.php
deleted file mode 100644 (file)
index b894e2d..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-class IPF_ORM_Connection_Module
-{
-    protected $conn;
-    protected $moduleName;
-
-    public function __construct($conn = null)
-    {
-        if ( ! ($conn instanceof IPF_ORM_Connection)) {
-            $conn = IPF_ORM_Manager::connection();
-        }
-        $this->conn = $conn;
-
-        $e = explode('_', get_class($this));
-
-        $this->moduleName = $e[1];
-    }
-
-    public function getConnection()
-    {
-        return $this->conn;
-    }
-
-    public function getModuleName()
-    {
-        return $this->moduleName;
-    }
-}
diff --git a/ipf/legacy_orm/orm/connection/mysql.php b/ipf/legacy_orm/orm/connection/mysql.php
deleted file mode 100644 (file)
index 4195f87..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-<?php
-
-class IPF_ORM_Connection_Mysql extends IPF_ORM_Connection
-{
-    protected $driverName = 'Mysql';
-
-    protected static $keywords = array(
-        'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC',
-        'ASENSITIVE', 'BEFORE', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB',
-        'BOTH', 'BY', 'BIT', 'CALL', 'CASCADE', 'CASE', 'CHANGE',
-        'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN',
-        'CONDITION', 'CONNECTION', 'CONSTRAINT', 'CONTINUE',
-        'CONVERT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME',
-        'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE',
-        'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE',
-        'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT',
-        'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC',
-        'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL',
-        'EACH', 'ELSE', 'ELSEIF', 'ENCLOSED', 'ESCAPED', 'EXISTS',
-        'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FLOAT', 'FLOAT4',
-        'FLOAT8', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT',
-        'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY',
-        'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF',
-        'IGNORE', 'IN', 'INDEX', 'INFILE', 'INNER', 'INOUT',
-        'INSENSITIVE', 'INSERT', 'INT', 'INT1', 'INT2', 'INT3',
-        'INT4', 'INT8', 'INTEGER', 'INTERVAL', 'INTO', 'IS',
-        'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LEADING', 'LEAVE',
-        'LEFT', 'LIKE', 'LIMIT', 'LINES', 'LOAD', 'LOCALTIME',
-        'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT',
-        'LOOP', 'LOW_PRIORITY', 'MATCH', 'MEDIUMBLOB', 'MEDIUMINT',
-        'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND',
-        'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NOT',
-        'NO_WRITE_TO_BINLOG', 'NULL', 'NUMERIC', 'ON', 'OPTIMIZE',
-        'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER',
-        'OUTFILE', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE',
-        'RAID0', 'READ', 'READS', 'REAL', 'REFERENCES', 'REGEXP',
-        'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE',
-        'RESTRICT', 'RETURN', 'REVOKE', 'RIGHT', 'RLIKE', 'SCHEMA',
-        'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE',
-        'SEPARATOR', 'SET', 'SHOW', 'SMALLINT', 'SONAME', 'SPATIAL',
-        'SPECIFIC', 'SQL', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING',
-        'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT',
-        'SSL', 'STARTING', 'STRAIGHT_JOIN', 'TABLE', 'TERMINATED',
-        'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING',
-        'TRIGGER', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK',
-        'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE',
-        'UTC_TIME', 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR',
-        'VARCHARACTER', 'VARYING', 'WHEN', 'WHERE', 'WHILE', 'WITH',
-        'WRITE', 'X509', 'XOR', 'YEAR_MONTH', 'ZEROFILL'
-    );
-
-    public function __construct(IPF_ORM_Manager $manager, $adapter)
-    {
-        $this->attributes[IPF_ORM::ATTR_DEFAULT_TABLE_TYPE] = 'INNODB';
-
-        $this->properties['string_quoting'] = array('start' => "'",
-                                                    'end' => "'",
-                                                    'escape' => '\\',
-                                                    'escape_pattern' => '\\');
-
-        $this->properties['identifier_quoting'] = '`';
-
-        $this->properties['sql_comments'] = array(
-                                            array('start' => '-- ', 'end' => "\n", 'escape' => false),
-                                            array('start' => '#', 'end' => "\n", 'escape' => false),
-                                            array('start' => '/*', 'end' => '*/', 'escape' => false),
-                                            );
-
-        $this->properties['varchar_max_length'] = 255;
-
-        parent::__construct($manager, $adapter);
-    }
-
-    public function quoteIdentifier($str)
-    {
-        $quote = $this->identifier_quoting;
-        $q = array();
-        foreach (explode('.', $str) as $s) {
-            if (in_array(strtoupper($s), self::$keywords))
-                $q[] = $quote . str_replace($quote, $quote . $quote, $s) . $quote;
-            else
-                $q[] = $s;
-        }
-        return implode('.', $q);
-    }
-
-    public function getDatabaseName()
-    {
-        return $this->fetchOne('SELECT DATABASE()');
-    }
-
-    public function replace(IPF_ORM_Table $table, array $fields, array $keys)
-    {
-        if (empty($keys)) {
-            throw new IPF_ORM_Exception('Not specified which fields are keys');
-        }
-        $columns = array();
-        $values = array();
-        $params = array();
-        foreach ($fields as $fieldName => $value) {
-            $columns[] = $table->getColumnName($fieldName);
-            $values[] = '?';
-            $params[] = $value;
-        }
-        $query = 'REPLACE INTO ' . $table->getTableName() . ' (' . implode(',', $columns) . ') VALUES (' . implode(',', $values) . ')';
-        return $this->exec($query, $params);
-    }
-
-    public function modifyLimitQuery($query, $limit = false,$offset = false,$isManip=false)
-    {
-        $limit = (int) $limit;
-        $offset = (int) $offset;
-        
-        if ($limit && $offset) {
-            $query .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
-        } elseif ($limit && ! $offset) {
-            $query .= ' LIMIT ' . $limit;
-        } elseif ( ! $limit && $offset) {
-            $query .= ' LIMIT 999999999999 OFFSET ' . $offset;
-        }
-        return $query;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/connection/statement.php b/ipf/legacy_orm/orm/connection/statement.php
deleted file mode 100644 (file)
index cfea7cc..0000000
+++ /dev/null
@@ -1,186 +0,0 @@
-<?php
-
-class IPF_ORM_Connection_Statement
-{
-    protected $_conn;
-    protected $_stmt;
-    public function __construct(IPF_ORM_Connection $conn, $stmt)
-    {
-        $this->_conn = $conn;
-        $this->_stmt = $stmt;
-
-        if ($stmt === false) {
-            throw new IPF_ORM_Exception('Unknown statement object given.');
-        }
-    }
-
-    public function getConnection()
-    {
-        return $this->_conn;
-    }
-    public function getStatement()
-    {
-        return $this->_stmt;
-    }
-    public function getQuery()
-    {
-        return $this->_stmt->queryString;
-    }
-
-    public function bindColumn($column, $param, $type = null)
-    {
-        if ($type === null) {
-            return $this->_stmt->bindColumn($column, $param);
-        } else {
-            return $this->_stmt->bindColumn($column, $param, $type);
-        }
-    }
-
-    public function bindValue($param, $value, $type = null)
-    {
-        if ($type === null) {
-            return $this->_stmt->bindValue($param, $value);
-        } else {
-            return $this->_stmt->bindValue($param, $value, $type);
-        }
-    }
-
-    public function bindParam($column, &$variable, $type = null, $length = null, $driverOptions = array())
-    {
-        if ($type === null) {
-            return $this->_stmt->bindParam($column, $variable);
-        } else {
-            return $this->_stmt->bindParam($column, $variable, $type, $length, $driverOptions);
-        }
-    }
-
-    public function closeCursor()
-    {
-        return $this->_stmt->closeCursor();
-    }
-
-    public function columnCount()
-    {
-        return $this->_stmt->columnCount();
-    }
-
-    public function errorCode()
-    {
-        return $this->_stmt->errorCode();
-    }
-
-    public function errorInfo()
-    {
-        return $this->_stmt->errorInfo();
-    }
-
-    public function execute($params = null)
-    {
-        try {
-            $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_EXECUTE, $this->getQuery(), $params);
-            $this->_conn->notifyDBListeners('preStmtExecute', $event);
-
-            $result = true;
-            if ( ! $event->skipOperation) {
-                $result = $this->_stmt->execute($params);
-                $this->_conn->incrementQueryCount();
-            }
-
-            $this->_conn->notifyDBListeners('postStmtExecute', $event);
-
-            return $result;
-        } catch (PDOException $e) {
-        } catch (IPF_ORM_Exception_Adapter $e) {
-        }
-
-        $this->_conn->rethrowException($e, $this);
-
-        return false;
-    }
-
-    public function fetch($fetchMode = IPF_ORM::FETCH_BOTH,
-                          $cursorOrientation = IPF_ORM::FETCH_ORI_NEXT,
-                          $cursorOffset = null)
-    {
-        $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_FETCH, $this->getQuery());
-
-        $event->fetchMode = $fetchMode;
-        $event->cursorOrientation = $cursorOrientation;
-        $event->cursorOffset = $cursorOffset;
-
-        $data = $this->_conn->notifyDBListeners('preFetch', $event);
-
-        if ( ! $event->skipOperation) {
-            $data = $this->_stmt->fetch($fetchMode, $cursorOrientation, $cursorOffset);
-        }
-
-        $this->_conn->notifyDBListeners('postFetch', $event);
-
-        return $data;
-    }
-
-    public function fetchAll($fetchMode = IPF_ORM::FETCH_BOTH,
-                             $columnIndex = null)
-    {
-        $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_FETCHALL, $this->getQuery());
-        $event->fetchMode = $fetchMode;
-        $event->columnIndex = $columnIndex;
-
-        $this->_conn->notifyDBListeners('preFetchAll', $event);
-
-        if ( ! $event->skipOperation) {
-            if ($columnIndex !== null) {
-                $data = $this->_stmt->fetchAll($fetchMode, $columnIndex);
-            } else {
-                $data = $this->_stmt->fetchAll($fetchMode);
-            }
-
-            $event->data = $data;
-        }
-
-        $this->_conn->notifyDBListeners('postFetchAll', $event);
-
-        return $data;
-    }
-
-    public function fetchColumn($columnIndex = 0)
-    {
-        return $this->_stmt->fetchColumn($columnIndex);
-    }
-
-    public function fetchObject($className = 'stdClass', $args = array())
-    {
-        return $this->_stmt->fetchObject($className, $args);
-    }
-
-    public function getAttribute($attribute)
-    {
-        return $this->_stmt->getAttribute($attribute);
-    }
-
-    public function getColumnMeta($column)
-    {
-        return $this->_stmt->getColumnMeta($column);
-    }
-
-    public function nextRowset()
-    {
-        return $this->_stmt->nextRowset();
-    }
-
-    public function rowCount()
-    {
-        return $this->_stmt->rowCount();
-    }
-
-    public function setAttribute($attribute, $value)
-    {
-        return $this->_stmt->setAttribute($attribute, $value);
-    }
-
-    public function setFetchMode($mode, $arg1 = null, $arg2 = null)
-    {
-        return $this->_stmt->setFetchMode($mode, $arg1, $arg2);
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/connection/unitofwork.php b/ipf/legacy_orm/orm/connection/unitofwork.php
deleted file mode 100644 (file)
index 301b5d7..0000000
+++ /dev/null
@@ -1,574 +0,0 @@
-<?php
-
-class IPF_ORM_Connection_UnitOfWork extends IPF_ORM_Connection_Module
-{
-    public function saveGraph(IPF_ORM_Record $record)
-    {
-        $conn = $this->getConnection();
-
-        $state = $record->state();
-        if ($state === IPF_ORM_Record::STATE_LOCKED) {
-            return false;
-        }
-
-        $record->state(IPF_ORM_Record::STATE_LOCKED);
-
-        $conn->beginInternalTransaction();
-        $saveLater = $this->saveRelated($record);
-
-        $record->state($state);
-
-        if ($record->isValid()) {
-            $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_SAVE);
-            $record->preSave($event);
-            $record->getTable()->notifyRecordListeners('preSave', $event);
-            $state = $record->state();
-
-            if ( ! $event->skipOperation) {
-                switch ($state) {
-                    case IPF_ORM_Record::STATE_TDIRTY:
-                        $this->insert($record);
-                        break;
-                    case IPF_ORM_Record::STATE_DIRTY:
-                    case IPF_ORM_Record::STATE_PROXY:
-                        $this->update($record);
-                        break;
-                    case IPF_ORM_Record::STATE_CLEAN:
-                    case IPF_ORM_Record::STATE_TCLEAN:
-                        break;
-                }
-            }
-
-            // NOTE: what about referential integrity issues?
-            foreach ($record->getPendingDeletes() as $pendingDelete) {
-                $pendingDelete->delete();
-            }
-
-            $record->getTable()->notifyRecordListeners('postSave', $event);
-            $record->postSave($event);
-        } else {
-            $conn->transaction->addInvalid($record);
-        }
-
-        $state = $record->state();
-
-        $record->state(IPF_ORM_Record::STATE_LOCKED);
-
-        foreach ($saveLater as $fk) {
-            $alias = $fk->getAlias();
-
-            if ($record->hasReference($alias)) {
-                $obj = $record->$alias;
-                // check that the related object is not an instance of IPF_ORM_Null
-                if ( ! ($obj instanceof IPF_ORM_Null)) {
-                    $obj->save($conn);
-                }
-            }
-        }
-
-        // save the MANY-TO-MANY associations
-        $this->saveAssociations($record);
-
-        $record->state($state);
-        $conn->commit();
-
-        return true;
-    }
-
-    public function save(IPF_ORM_Record $record)
-    {
-        $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_SAVE);
-
-        $record->preSave($event);
-
-        $record->getTable()->notifyRecordListeners('preSave', $event);
-
-        if ( ! $event->skipOperation) {
-            switch ($record->state()) {
-                case IPF_ORM_Record::STATE_TDIRTY:
-                    $this->insert($record);
-                    break;
-                case IPF_ORM_Record::STATE_DIRTY:
-                case IPF_ORM_Record::STATE_PROXY:
-                    $this->update($record);
-                    break;
-                case IPF_ORM_Record::STATE_CLEAN:
-                case IPF_ORM_Record::STATE_TCLEAN:
-                    // do nothing
-                    break;
-            }
-        }
-
-        $record->getTable()->notifyRecordListeners('postSave', $event);
-
-        $record->postSave($event);
-    }
-
-    public function delete(IPF_ORM_Record $record)
-    {
-        $deletions = array();
-        $this->_collectDeletions($record, $deletions);
-        return $this->_executeDeletions($deletions);
-    }
-
-    private function _collectDeletions(IPF_ORM_Record $record, array &$deletions)
-    {
-        if ( ! $record->exists()) {
-            return;
-        }
-
-        $deletions[$record->getOid()] = $record;
-        $this->_cascadeDelete($record, $deletions);
-    }
-
-    private function _executeDeletions(array $deletions)
-    {
-        // collect class names
-        $classNames = array();
-        foreach ($deletions as $record) {
-            $classNames[] = $record->getTable()->getComponentName();
-        }
-        $classNames = array_unique($classNames);
-
-        // order deletes
-        $executionOrder = $this->buildFlushTree($classNames);
-
-        // execute
-        try {
-            $this->conn->beginInternalTransaction();
-
-            for ($i = count($executionOrder) - 1; $i >= 0; $i--) {
-                $className = $executionOrder[$i];
-                $table = $this->conn->getTable($className);
-
-                // collect identifiers
-                $identifierMaps = array();
-                $deletedRecords = array();
-                foreach ($deletions as $oid => $record) {
-                    if ($record->getTable()->getComponentName() == $className) {
-                        $veto = $this->_preDelete($record);
-                        if ( ! $veto) {
-                            $identifierMaps[] = $record->identifier();
-                            $deletedRecords[] = $record;
-                            unset($deletions[$oid]);
-                        }
-                    }
-                }
-
-                if (count($deletedRecords) < 1) {
-                    continue;
-                }
-
-                // extract query parameters (only the identifier values are of interest)
-                $params = array();
-                $columnNames = array();
-                foreach ($identifierMaps as $idMap) {
-                    while (list($fieldName, $value) = each($idMap)) {
-                        $params[] = $value;
-                        $columnNames[] = $table->getColumnName($fieldName);
-                    }
-                }
-                $columnNames = array_unique($columnNames);
-
-                // delete
-                $tableName = $table->getTableName();
-                $sql = "DELETE FROM " . $this->conn->quoteIdentifier($tableName) . " WHERE ";
-
-                if ($table->isIdentifierComposite()) {
-                    $sql .= $this->_buildSqlCompositeKeyCondition($columnNames, count($identifierMaps));
-                    $this->conn->exec($sql, $params);
-                } else {
-                    $sql .= $this->_buildSqlSingleKeyCondition($columnNames, count($params));
-                    $this->conn->exec($sql, $params);
-                }
-
-                // adjust state, remove from identity map and inform postDelete listeners
-                foreach ($deletedRecords as $record) {
-                    $record->state(IPF_ORM_Record::STATE_TCLEAN);
-                    $record->getTable()->removeRecord($record);
-                    $this->_postDelete($record);
-                }
-            }
-
-            $this->conn->commit();
-            // trigger postDelete for records skipped during the deletion (veto!)
-            foreach ($deletions as $skippedRecord) {
-                $this->_postDelete($skippedRecord);
-            }
-
-            return true;
-        } catch (Exception $e) {
-            $this->conn->rollback();
-            throw $e;
-        }
-    }
-
-    private function _buildSqlSingleKeyCondition($columnNames, $numRecords)
-    {
-        $idColumn = $this->conn->quoteIdentifier($columnNames[0]);
-        return implode(' OR ', array_fill(0, $numRecords, "$idColumn = ?"));
-    }
-
-    private function _buildSqlCompositeKeyCondition($columnNames, $numRecords)
-    {
-        $singleCondition = "";
-        foreach ($columnNames as $columnName) {
-            $columnName = $this->conn->quoteIdentifier($columnName);
-            if ($singleCondition === "") {
-                $singleCondition .= "($columnName = ?";
-            } else {
-                $singleCondition .= " AND $columnName = ?";
-            }
-        }
-        $singleCondition .= ")";
-        $fullCondition = implode(' OR ', array_fill(0, $numRecords, $singleCondition));
-
-        return $fullCondition;
-    }
-
-    protected function _cascadeDelete(IPF_ORM_Record $record, array &$deletions)
-    {
-        foreach ($record->getTable()->getRelations() as $relation) {
-             if ($relation->isCascadeDelete()) {
-                 $fieldName = $relation->getAlias();
-                 // if it's a xToOne relation and the related object is already loaded
-                 // we don't need to refresh.
-                 if ( ! ($relation->getType() == IPF_ORM_Relation::ONE && isset($record->$fieldName))) {
-                     $record->refreshRelated($relation->getAlias());
-                 }
-                 $relatedObjects = $record->get($relation->getAlias());
-                 if ($relatedObjects instanceof IPF_ORM_Record && $relatedObjects->exists()
-                        && ! isset($deletions[$relatedObjects->getOid()])) {
-                     $this->_collectDeletions($relatedObjects, $deletions);
-                 } else if ($relatedObjects instanceof IPF_ORM_Collection && count($relatedObjects) > 0) {
-                     // cascade the delete to the other objects
-                     foreach ($relatedObjects as $object) {
-                         if ( ! isset($deletions[$object->getOid()])) {
-                             $this->_collectDeletions($object, $deletions);
-                         }
-                     }
-                 }
-             }
-         }
-     }
-
-    public function saveRelated(IPF_ORM_Record $record)
-    {
-        $saveLater = array();
-        foreach ($record->getReferences() as $k => $v) {
-            $rel = $record->getTable()->getRelation($k);
-
-            $local = $rel->getLocal();
-            $foreign = $rel->getForeign();
-
-            if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
-                $saveLater[$k] = $rel;
-            } else if ($rel instanceof IPF_ORM_Relation_LocalKey) {
-                // ONE-TO-ONE relationship
-                $obj = $record->get($rel->getAlias());
-
-                // Protection against infinite function recursion before attempting to save
-                if ($obj instanceof IPF_ORM_Record && $obj->isModified()) {
-                    $obj->save($this->conn);
-
-                    /** Can this be removed?
-                    $id = array_values($obj->identifier());
-
-                    foreach ((array) $rel->getLocal() as $k => $field) {
-                        $record->set($field, $id[$k]);
-                    }
-                    */
-                }
-            }
-        }
-
-        return $saveLater;
-    }
-
-    public function saveAssociations(IPF_ORM_Record $record)
-    {
-        foreach ($record->getReferences() as $k => $v) {
-            $rel = $record->getTable()->getRelation($k);
-            //print get_class($rel);
-            if ($rel instanceof IPF_ORM_Relation_Association) {
-                $v->save($this->conn);
-
-                $assocTable = $rel->getAssociationTable();
-
-                foreach ($v->getDeleteDiff() as $r) {
-                    $query = 'DELETE FROM ' . $assocTable->getTableName()
-                           . ' WHERE ' . $rel->getForeign() . ' = ?'
-                           . ' AND ' . $rel->getLocal() . ' = ?';
-
-                    $this->conn->execute($query, array($r->getIncremented(), $record->getIncremented()));
-                }
-
-                foreach ($v->getInsertDiff() as $r) {
-                    $assocRecord = $assocTable->create();
-                    $assocRecord->set($assocTable->getFieldName($rel->getForeign()), $r);
-                    $assocRecord->set($assocTable->getFieldName($rel->getLocal()), $record);
-                    $this->saveGraph($assocRecord);
-                }
-            }
-        }
-    }
-
-    private function _preDelete(IPF_ORM_Record $record)
-    {
-        $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_DELETE);
-        $record->preDelete($event);
-        $record->getTable()->notifyRecordListeners('preDelete', $event);
-
-        return $event->skipOperation;
-    }
-
-    private function _postDelete(IPF_ORM_Record $record)
-    {
-        $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_DELETE);
-        $record->postDelete($event);
-        $record->getTable()->notifyRecordListeners('postDelete', $event);
-    }
-
-    public function saveAll()
-    {
-        // get the flush tree
-        $tree = $this->buildFlushTree($this->conn->getTables());
-
-        // save all records
-        foreach ($tree as $name) {
-            $table = $this->conn->getTable($name);
-            foreach ($table->getRepository() as $record) {
-                $this->saveGraph($record);
-            }
-        }
-    }
-
-    public function update(IPF_ORM_Record $record)
-    {
-        $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_UPDATE);
-        $record->preUpdate($event);
-        $table = $record->getTable();
-        $table->notifyRecordListeners('preUpdate', $event);
-
-        if ( ! $event->skipOperation) {
-            $identifier = $record->identifier();
-            $array = $record->getPrepared();
-            $this->conn->update($table, $array, $identifier);
-            $record->assignIdentifier(true);
-        }
-
-        $table->notifyRecordListeners('postUpdate', $event);
-
-        $record->postUpdate($event);
-
-        return true;
-    }
-
-    public function insert(IPF_ORM_Record $record)
-    {
-        // listen the onPreInsert event
-        $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_INSERT);
-        $record->preInsert($event);
-        $table = $record->getTable();
-        $table->notifyRecordListeners('preInsert', $event);
-
-        if ( ! $event->skipOperation) {
-            $this->processSingleInsert($record);
-        }
-
-        $table->addRecord($record);
-        $table->notifyRecordListeners('postInsert', $event);
-        $record->postInsert($event);
-
-        return true;
-    }
-
-    public function processSingleInsert(IPF_ORM_Record $record)
-    {
-        $fields = $record->getPrepared();
-        $table = $record->getTable();
-
-        // Populate fields with a blank array so that a blank records can be inserted
-        if (empty($fields)) {
-            foreach ($table->getFieldNames() as $field) {
-                $fields[$field] = null;
-            }
-        }
-
-        $identifier = (array) $table->getIdentifier();
-
-        $this->conn->insert($table, $fields);
-
-        if (count($identifier) == 1 && $table->getIdentifierType() !== IPF_ORM::IDENTIFIER_NATURAL) {
-            $id = $this->conn->lastInsertId();
-            if (!$id)
-                throw new IPF_ORM_Exception("Couldn't get last insert identifier.");
-
-            $record->assignIdentifier($id);
-        } else {
-            $record->assignIdentifier(true);
-        }
-    }
-
-    public function buildFlushTree(array $tables)
-    {
-        // determine classes to order. only necessary because the $tables param
-        // can contain strings or table objects...
-        $classesToOrder = array();
-        foreach ($tables as $table) {
-            if ( ! ($table instanceof IPF_ORM_Table)) {
-                $table = $this->conn->getTable($table, false);
-            }
-            $classesToOrder[] = $table->getComponentName();
-        }
-        $classesToOrder = array_unique($classesToOrder);
-
-        if (count($classesToOrder) < 2) {
-            return $classesToOrder;
-        }
-
-        // build the correct order
-        $flushList = array();
-        foreach ($classesToOrder as $class) {
-            $table = $this->conn->getTable($class, false);
-            $currentClass = $table->getComponentName();
-
-            $index = array_search($currentClass, $flushList);
-
-            if ($index === false) {
-                //echo "adding $currentClass to flushlist";
-                $flushList[] = $currentClass;
-                $index = max(array_keys($flushList));
-            }
-
-            $rels = $table->getRelations();
-
-            // move all foreignkey relations to the beginning
-            foreach ($rels as $key => $rel) {
-                if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
-                    unset($rels[$key]);
-                    array_unshift($rels, $rel);
-                }
-            }
-
-            foreach ($rels as $rel) {
-                $relatedClassName = $rel->getTable()->getComponentName();
-
-                if ( ! in_array($relatedClassName, $classesToOrder)) {
-                    continue;
-                }
-
-                $relatedCompIndex = array_search($relatedClassName, $flushList);
-                $type = $rel->getType();
-
-                // skip self-referenced relations
-                if ($relatedClassName === $currentClass) {
-                    continue;
-                }
-
-                if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
-                    // the related component needs to come after this component in
-                    // the list (since it holds the fk)
-
-                    if ($relatedCompIndex !== false) {
-                        // the component is already in the list
-                        if ($relatedCompIndex >= $index) {
-                            // it's already in the right place
-                            continue;
-                        }
-
-                        unset($flushList[$index]);
-                        // the related comp has the fk. so put "this" comp immediately
-                        // before it in the list
-                        array_splice($flushList, $relatedCompIndex, 0, $currentClass);
-                        $index = $relatedCompIndex;
-                    } else {
-                        $flushList[] = $relatedClassName;
-                    }
-
-                } else if ($rel instanceof IPF_ORM_Relation_LocalKey) {
-                    // the related component needs to come before the current component
-                    // in the list (since this component holds the fk).
-
-                    if ($relatedCompIndex !== false) {
-                        // already in flush list
-                        if ($relatedCompIndex <= $index) {
-                            // it's in the right place
-                            continue;
-                        }
-
-                        unset($flushList[$relatedCompIndex]);
-                        // "this" comp has the fk. so put the related comp before it
-                        // in the list
-                        array_splice($flushList, $index, 0, $relatedClassName);
-                    } else {
-                        array_unshift($flushList, $relatedClassName);
-                        $index++;
-                    }
-                } else if ($rel instanceof IPF_ORM_Relation_Association) {
-                    // the association class needs to come after both classes
-                    // that are connected through it in the list (since it holds
-                    // both fks)
-
-                    $assocTable = $rel->getAssociationFactory();
-                    $assocClassName = $assocTable->getComponentName();
-
-                    if ($relatedCompIndex !== false) {
-                        unset($flushList[$relatedCompIndex]);
-                    }
-
-                    array_splice($flushList, $index, 0, $relatedClassName);
-                    $index++;
-
-                    $index3 = array_search($assocClassName, $flushList);
-
-                    if ($index3 !== false) {
-                        if ($index3 >= $index) {
-                            continue;
-                        }
-
-                        unset($flushList[$index]);
-                        array_splice($flushList, $index3, 0, $assocClassName);
-                        $index = $relatedCompIndex;
-                    } else {
-                        $flushList[] = $assocClassName;
-                    }
-                }
-            }
-        }
-
-        return array_values($flushList);
-    }
-
-    private function _formatDataSet(IPF_ORM_Record $record)
-    {
-        $table = $record->getTable();
-        $dataSet = array();
-        $component = $table->getComponentName();
-        $array = $record->getPrepared();
-
-        foreach ($table->getColumns() as $columnName => $definition) {
-            if ( ! isset($dataSet[$component])) {
-                $dataSet[$component] = array();
-            }
-
-            $fieldName = $table->getFieldName($columnName);
-            if (isset($definition['primary']) && $definition['primary']) {
-                continue;
-            }
-
-            if ( ! array_key_exists($fieldName, $array)) {
-                continue;
-            }
-
-            if (isset($definition['owner'])) {
-                $dataSet[$definition['owner']][$fieldName] = $array[$fieldName];
-            } else {
-                $dataSet[$component][$fieldName] = $array[$fieldName];
-            }
-        }
-
-        return $dataSet;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/event.php b/ipf/legacy_orm/orm/event.php
deleted file mode 100644 (file)
index c52fd29..0000000
+++ /dev/null
@@ -1,194 +0,0 @@
-<?php
-
-class IPF_ORM_Event
-{
-    const CONN_QUERY         = 1;
-    const CONN_EXEC          = 2;
-    const CONN_PREPARE       = 3;
-    const CONN_CONNECT       = 4;
-    const CONN_CLOSE         = 5;
-    const CONN_ERROR         = 6;
-
-    const STMT_EXECUTE       = 10;
-    const STMT_FETCH         = 11;
-    const STMT_FETCHALL      = 12;
-
-    const TX_BEGIN           = 31;
-    const TX_COMMIT          = 32;
-    const TX_ROLLBACK        = 33;
-    const SAVEPOINT_CREATE   = 34;
-    const SAVEPOINT_ROLLBACK = 35;
-    const SAVEPOINT_COMMIT   = 36;
-
-    const HYDRATE            = 40;
-
-    const RECORD_DELETE      = 21;
-    const RECORD_SAVE        = 22;
-    const RECORD_UPDATE      = 23;
-    const RECORD_INSERT      = 24;
-    const RECORD_SERIALIZE   = 25;
-    const RECORD_UNSERIALIZE = 26;
-    const RECORD_DQL_SELECT  = 28;
-    const RECORD_DQL_DELETE  = 27;
-    const RECORD_DQL_UPDATE  = 29;
-
-    protected $_invoker;
-
-    protected $_query;
-
-    protected $_params;
-
-    protected $_code;
-
-    protected $_startedMicrotime;
-
-    protected $_endedMicrotime;
-
-    protected $_options = array();
-
-    public function __construct($invoker, $code, $query = null, $params = array())
-    {
-        $this->_invoker = $invoker;
-        $this->_code    = $code;
-        $this->_query   = $query;
-        $this->_params  = $params;
-    }
-
-    public function getQuery()
-    {
-        return $this->_query;
-    }
-
-    public function getName()
-    {
-        switch ($this->_code) {
-            case self::CONN_QUERY:
-                return 'query';
-            case self::CONN_EXEC:
-                return 'exec';
-            case self::CONN_PREPARE:
-                return 'prepare';
-            case self::CONN_CONNECT:
-                return 'connect';
-            case self::CONN_CLOSE:
-                return 'close';
-            case self::CONN_ERROR:
-                return 'error';
-
-            case self::STMT_EXECUTE:
-                return 'execute';
-            case self::STMT_FETCH:
-                return 'fetch';
-            case self::STMT_FETCHALL:
-                return 'fetch all';
-
-            case self::TX_BEGIN:
-                return 'begin';
-            case self::TX_COMMIT:
-                return 'commit';
-            case self::TX_ROLLBACK:
-                return 'rollback';
-
-            case self::SAVEPOINT_CREATE:
-                return 'create savepoint';
-            case self::SAVEPOINT_ROLLBACK:
-                return 'rollback savepoint';
-            case self::SAVEPOINT_COMMIT:
-                return 'commit savepoint';
-
-            case self::RECORD_DELETE:
-                return 'delete record';
-            case self::RECORD_SAVE:
-                return 'save record';
-            case self::RECORD_UPDATE:
-                return 'update record';
-            case self::RECORD_INSERT:
-                return 'insert record';
-            case self::RECORD_SERIALIZE:
-                return 'serialize record';
-            case self::RECORD_UNSERIALIZE:
-                return 'unserialize record';
-            case self::RECORD_DQL_SELECT:
-                return 'select records';
-            case self::RECORD_DQL_DELETE:
-                return 'delete records';
-            case self::RECORD_DQL_UPDATE:
-                return 'update records';
-        }
-    }
-
-    public function getCode()
-    {
-        return $this->_code;
-    }
-
-    public function __get($option)
-    {
-        if ( ! isset($this->_options[$option])) {
-            return null;
-        }
-
-        return $this->_options[$option];
-    }
-
-    public function skipOperation()
-    {
-        $this->_options['skipOperation'] = true;
-
-        return $this;
-    }
-
-    public function __set($option, $value)
-    {
-        $this->_options[$option] = $value;
-
-        return $this;
-    }
-
-    public function set($option, &$value)
-    {
-        $this->_options[$option] =& $value;
-
-        return $this;
-    }
-
-    public function start()
-    {
-        $this->_startedMicrotime = microtime(true);
-    }
-
-    public function hasEnded()
-    {
-        return ($this->_endedMicrotime != null);
-    }
-
-    public function end()
-    {
-        $this->_endedMicrotime = microtime(true);
-
-        return $this;
-    }
-
-    public function getInvoker()
-    {
-        return $this->_invoker;
-    }
-
-    public function setInvoker($invoker)
-    {
-        $this->_invoker = $invoker;
-    }
-
-    public function getParams()
-    {
-        return $this->_params;
-    }
-
-    public function getElapsedSecs()
-    {
-        if (is_null($this->_endedMicrotime)) {
-            return false;
-        }
-        return ($this->_endedMicrotime - $this->_startedMicrotime);
-    }
-}
diff --git a/ipf/legacy_orm/orm/exception.php b/ipf/legacy_orm/orm/exception.php
deleted file mode 100644 (file)
index d7d84e9..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-
-class IPF_ORM_Exception extends IPF_Exception
-{
-    protected static $_errorMessages = array(
-        IPF_ORM::ERR                    => 'unknown error',
-        IPF_ORM::ERR_ALREADY_EXISTS     => 'already exists',
-        IPF_ORM::ERR_CANNOT_CREATE      => 'can not create',
-        IPF_ORM::ERR_CANNOT_ALTER       => 'can not alter',
-        IPF_ORM::ERR_CANNOT_REPLACE     => 'can not replace',
-        IPF_ORM::ERR_CANNOT_DELETE      => 'can not delete',
-        IPF_ORM::ERR_CANNOT_DROP        => 'can not drop',
-        IPF_ORM::ERR_CONSTRAINT         => 'constraint violation',
-        IPF_ORM::ERR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
-        IPF_ORM::ERR_DIVZERO            => 'division by zero',
-        IPF_ORM::ERR_INVALID            => 'invalid',
-        IPF_ORM::ERR_INVALID_DATE       => 'invalid date or time',
-        IPF_ORM::ERR_INVALID_NUMBER     => 'invalid number',
-        IPF_ORM::ERR_MISMATCH           => 'mismatch',
-        IPF_ORM::ERR_NODBSELECTED       => 'no database selected',
-        IPF_ORM::ERR_NOSUCHFIELD        => 'no such field',
-        IPF_ORM::ERR_NOSUCHTABLE        => 'no such table',
-        IPF_ORM::ERR_NOT_CAPABLE        => 'IPF backend not capable',
-        IPF_ORM::ERR_NOT_FOUND          => 'not found',
-        IPF_ORM::ERR_NOT_LOCKED         => 'not locked',
-        IPF_ORM::ERR_SYNTAX             => 'syntax error',
-        IPF_ORM::ERR_UNSUPPORTED        => 'not supported',
-        IPF_ORM::ERR_VALUE_COUNT_ON_ROW => 'value count on row',
-        IPF_ORM::ERR_INVALID_DSN        => 'invalid DSN',
-        IPF_ORM::ERR_CONNECT_FAILED     => 'connect failed',
-        IPF_ORM::ERR_NEED_MORE_DATA     => 'insufficient data supplied',
-        IPF_ORM::ERR_EXTENSION_NOT_FOUND=> 'extension not found',
-        IPF_ORM::ERR_NOSUCHDB           => 'no such database',
-        IPF_ORM::ERR_ACCESS_VIOLATION   => 'insufficient permissions',
-        IPF_ORM::ERR_LOADMODULE         => 'error while including on demand module',
-        IPF_ORM::ERR_TRUNCATED          => 'truncated',
-        IPF_ORM::ERR_DEADLOCK           => 'deadlock detected',
-    );
-
-    public function errorMessage($value = null)
-    {
-        if (is_null($value)) {
-            return self::$_errorMessages;
-        }
-
-        return isset(self::$_errorMessages[$value]) ?
-           self::$_errorMessages[$value] : self::$_errorMessages[IPF_ORM::ERR];
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/exception/adapter.php b/ipf/legacy_orm/orm/exception/adapter.php
deleted file mode 100644 (file)
index 7f8ce00..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-<?php
-
-class IPF_ORM_Exception_Adapter extends IPF_ORM_Exception{}
diff --git a/ipf/legacy_orm/orm/exception/connection.php b/ipf/legacy_orm/orm/exception/connection.php
deleted file mode 100644 (file)
index fc43bff..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php
-
-class IPF_ORM_Exception_Connection extends IPF_ORM_Exception
-{
-    static protected $errorMessages = array(
-                IPF_ORM::ERR                    => 'unknown error',
-                IPF_ORM::ERR_ALREADY_EXISTS     => 'already exists',
-                IPF_ORM::ERR_CANNOT_CREATE      => 'can not create',
-                IPF_ORM::ERR_CANNOT_ALTER       => 'can not alter',
-                IPF_ORM::ERR_CANNOT_REPLACE     => 'can not replace',
-                IPF_ORM::ERR_CANNOT_DELETE      => 'can not delete',
-                IPF_ORM::ERR_CANNOT_DROP        => 'can not drop',
-                IPF_ORM::ERR_CONSTRAINT         => 'constraint violation',
-                IPF_ORM::ERR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
-                IPF_ORM::ERR_DIVZERO            => 'division by zero',
-                IPF_ORM::ERR_INVALID            => 'invalid',
-                IPF_ORM::ERR_INVALID_DATE       => 'invalid date or time',
-                IPF_ORM::ERR_INVALID_NUMBER     => 'invalid number',
-                IPF_ORM::ERR_MISMATCH           => 'mismatch',
-                IPF_ORM::ERR_NODBSELECTED       => 'no database selected',
-                IPF_ORM::ERR_NOSUCHFIELD        => 'no such field',
-                IPF_ORM::ERR_NOSUCHTABLE        => 'no such table',
-                IPF_ORM::ERR_NOT_CAPABLE        => 'IPF_ORM backend not capable',
-                IPF_ORM::ERR_NOT_FOUND          => 'not found',
-                IPF_ORM::ERR_NOT_LOCKED         => 'not locked',
-                IPF_ORM::ERR_SYNTAX             => 'syntax error',
-                IPF_ORM::ERR_UNSUPPORTED        => 'not supported',
-                IPF_ORM::ERR_VALUE_COUNT_ON_ROW => 'value count on row',
-                IPF_ORM::ERR_INVALID_DSN        => 'invalid DSN',
-                IPF_ORM::ERR_CONNECT_FAILED     => 'connect failed',
-                IPF_ORM::ERR_NEED_MORE_DATA     => 'insufficient data supplied',
-                IPF_ORM::ERR_EXTENSION_NOT_FOUND=> 'extension not found',
-                IPF_ORM::ERR_NOSUCHDB           => 'no such database',
-                IPF_ORM::ERR_ACCESS_VIOLATION   => 'insufficient permissions',
-                IPF_ORM::ERR_LOADMODULE         => 'error while including on demand module',
-                IPF_ORM::ERR_TRUNCATED          => 'truncated',
-                IPF_ORM::ERR_DEADLOCK           => 'deadlock detected',
-                );
-
-    protected $portableCode;
-
-    public function getPortableCode()
-    {
-        return $this->portableCode;
-    }
-
-    public function getPortableMessage()
-    {
-        return self::errorMessage($this->portableCode);
-    }
-
-    public function errorMessage($value = null)
-    {
-        return isset(self::$errorMessages[$value]) ?
-           self::$errorMessages[$value] : self::$errorMessages[IPF_ORM::ERR];
-    }
-
-    public function processErrorInfo(array $errorInfo)
-    { }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/exception/locator.php b/ipf/legacy_orm/orm/exception/locator.php
deleted file mode 100644 (file)
index d39308f..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-<?php
-
-class IPF_ORM_Exception_Locator extends IPF_ORM_Exception_Base{}
diff --git a/ipf/legacy_orm/orm/exception/mysql.php b/ipf/legacy_orm/orm/exception/mysql.php
deleted file mode 100644 (file)
index 1d622df..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-class IPF_ORM_Exception_Mysql extends IPF_ORM_Exception_Connection
-{
-    protected static $errorCodeMap = array(
-                                      1004 => IPF_ORM::ERR_CANNOT_CREATE,
-                                      1005 => IPF_ORM::ERR_CANNOT_CREATE,
-                                      1006 => IPF_ORM::ERR_CANNOT_CREATE,
-                                      1007 => IPF_ORM::ERR_ALREADY_EXISTS,
-                                      1008 => IPF_ORM::ERR_CANNOT_DROP,
-                                      1022 => IPF_ORM::ERR_ALREADY_EXISTS,
-                                      1044 => IPF_ORM::ERR_ACCESS_VIOLATION,
-                                      1046 => IPF_ORM::ERR_NODBSELECTED,
-                                      1048 => IPF_ORM::ERR_CONSTRAINT,
-                                      1049 => IPF_ORM::ERR_NOSUCHDB,
-                                      1050 => IPF_ORM::ERR_ALREADY_EXISTS,
-                                      1051 => IPF_ORM::ERR_NOSUCHTABLE,
-                                      1054 => IPF_ORM::ERR_NOSUCHFIELD,
-                                      1061 => IPF_ORM::ERR_ALREADY_EXISTS,
-                                      1062 => IPF_ORM::ERR_ALREADY_EXISTS,
-                                      1064 => IPF_ORM::ERR_SYNTAX,
-                                      1091 => IPF_ORM::ERR_NOT_FOUND,
-                                      1100 => IPF_ORM::ERR_NOT_LOCKED,
-                                      1136 => IPF_ORM::ERR_VALUE_COUNT_ON_ROW,
-                                      1142 => IPF_ORM::ERR_ACCESS_VIOLATION,
-                                      1146 => IPF_ORM::ERR_NOSUCHTABLE,
-                                      1216 => IPF_ORM::ERR_CONSTRAINT,
-                                      1217 => IPF_ORM::ERR_CONSTRAINT,
-                                      1451 => IPF_ORM::ERR_CONSTRAINT,
-                                      );
-    public function processErrorInfo(array $errorInfo)
-    {
-        $code = $errorInfo[1];
-        if (isset(self::$errorCodeMap[$code])) {
-            $this->portableCode = self::$errorCodeMap[$code];
-            return true;
-        }
-        return false;
-    }
-}
diff --git a/ipf/legacy_orm/orm/exception/profiler.php b/ipf/legacy_orm/orm/exception/profiler.php
deleted file mode 100644 (file)
index 5fa9ad5..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-<?php
-
-class IPF_ORM_Exception_Profiler extends IPF_ORM_Exception{}
diff --git a/ipf/legacy_orm/orm/exception/validator.php b/ipf/legacy_orm/orm/exception/validator.php
deleted file mode 100644 (file)
index 597409b..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-class IPF_ORM_Exception_Validator extends IPF_ORM_Exception
-{
-    private $invalid = array();
-
-    public function __construct(array $invalid)
-    {
-        $this->invalid = $invalid;
-        parent::__construct($this->generateMessage());
-    }
-
-    public function getInvalidRecords()
-    {
-        return $this->invalid;
-    }
-
-    private function generateMessage()
-    {
-        $message = "";
-        foreach ($this->invalid as $record) {
-            $errors = array();
-            foreach ($record->getErrors() as $field => $validators)
-                $errors[] = 'Field "' . $field . '" failed following validators: ' . implode(', ', $validators) . '.';
-            $message .= "Validaton error in class " . get_class($record) . ' (' . implode(' ', $errors) . ') ';
-        }
-        return $message;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/export.php b/ipf/legacy_orm/orm/export.php
deleted file mode 100644 (file)
index a0cae94..0000000
+++ /dev/null
@@ -1,479 +0,0 @@
-<?php
-
-class IPF_ORM_Export extends IPF_ORM_Connection_Module
-{
-    protected $valid_default_values = array(
-        'text'      => '',
-        'boolean'   => true,
-        'integer'   => 0,
-        'decimal'   => 0.0,
-        'float'     => 0.0,
-        'double'    => 0.0,
-        'timestamp' => '1970-01-01 00:00:00',
-        'time'      => '00:00:00',
-        'date'      => '1970-01-01',
-        'clob'      => '',
-        'blob'      => '',
-        'string'    => ''
-    );
-
-    protected function getIndexName($name)
-    {
-        return $name . '_idx';
-    }
-
-    public function dropTableSql($table)
-    {
-        return 'DROP TABLE ' . $this->conn->quoteIdentifier($table);
-    }
-
-    public function dropTable($table)
-    {
-        $this->conn->execute($this->dropTableSql($table));
-    }
-
-    public function dropIndex($table, $name)
-    {
-        return $this->conn->exec($this->dropIndexSql($table, $name));
-    }
-
-    public function dropIndexSql($table, $name)
-    {
-        $name = $this->conn->quoteIdentifier($this->getIndexName($name));
-        
-        return 'DROP INDEX ' . $name;
-    }
-
-    public function dropConstraint($table, $name, $primary = false)
-    {
-        $table = $this->conn->quoteIdentifier($table);
-        $name  = $this->conn->quoteIdentifier($name);
-        
-        return $this->conn->exec('ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name);
-    }
-
-    public function dropForeignKey($table, $name)
-    {
-        return $this->dropConstraint($table, $name);
-    }
-
-    public function createConstraint($table, $name, $definition)
-    {
-        $sql = $this->createConstraintSql($table, $name, $definition);
-        
-        return $this->conn->exec($sql);
-    }
-
-    public function createConstraintSql($table, $name, $definition)
-    {
-        $table = $this->conn->quoteIdentifier($table);
-        $name  = $this->conn->quoteIdentifier($this->getIndexName($name));
-        $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $name;
-
-        if (isset($definition['primary']) && $definition['primary']) {
-            $query .= ' PRIMARY KEY';
-        } elseif (isset($definition['unique']) && $definition['unique']) {
-            $query .= ' UNIQUE';
-        }
-
-        $fields = array();
-        foreach (array_keys($definition['fields']) as $field) {
-            $fields[] = $this->conn->quoteIdentifier($field);
-        }
-        $query .= ' ('. implode(', ', $fields) . ')';
-
-        return $query;
-    }
-
-    public function createIndex($table, $name, array $definition)
-    {
-        return $this->conn->execute($this->createIndexSql($table, $name, $definition));
-    }
-
-    public function createIndexSql($table, $name, array $definition)
-    {
-        $table  = $this->conn->quoteIdentifier($table);
-        $name   = $this->conn->quoteIdentifier($name);
-        $type   = '';
-
-        if (isset($definition['type'])) {
-            switch (strtolower($definition['type'])) {
-                case 'unique':
-                    $type = strtoupper($definition['type']) . ' ';
-                break;
-                default:
-                    throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']);
-            }
-        }
-
-        $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table;
-
-        $fields = array();
-        foreach ($definition['fields'] as $field) {
-            $fields[] = $this->conn->quoteIdentifier($field);
-        }
-        $query .= ' (' . implode(', ', $fields) . ')';
-
-        return $query;
-    }    
-
-    public function createForeignKeySql($table, array $definition)
-    {
-        $table = $this->conn->quoteIdentifier($table);
-
-        $query = 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclaration($definition);
-
-        return $query;
-    }
-
-    public function createForeignKey($table, array $definition)
-    {
-        $sql = $this->createForeignKeySql($table, $definition);
-        
-        return $this->conn->execute($sql);
-    }
-
-    public function alterTable($name, array $changes, $check = false)
-    {
-        $sql = $this->alterTableSql($name, $changes, $check);
-        
-        if (is_string($sql) && $sql) {
-            $this->conn->execute($sql);
-        }
-    }
-
-    public function alterTableSql($name, array $changes, $check = false)
-    {
-        throw new IPF_ORM_Exception('Alter table not supported by this driver.');
-    }
-
-    public function getFieldDeclarationList(array $fields)
-    {
-        foreach ($fields as $fieldName => $field) {
-            $query = $this->getDeclaration($fieldName, $field);
-
-            $queryFields[] = $query;
-        }
-        return implode(', ', $queryFields);
-    }
-
-    public function getCheckDeclaration(array $definition)
-    {
-        $constraints = array();
-        foreach ($definition as $field => $def) {
-            if (is_string($def)) {
-                $constraints[] = 'CHECK (' . $def . ')';
-            } else {
-                if (isset($def['min'])) {
-                    $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')';
-                }
-
-                if (isset($def['max'])) {
-                    $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')';
-                }
-            }
-        }
-
-        return implode(', ', $constraints);
-    }
-
-    public function getIndexDeclaration($name, array $definition)
-    {
-        $name   = $this->conn->quoteIdentifier($name);
-        $type   = '';
-
-        if (isset($definition['type'])) {
-            if (strtolower($definition['type']) == 'unique') {
-                $type = strtoupper($definition['type']) . ' ';
-            } else {
-                throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']);
-            }
-        }
-
-        if ( ! isset($definition['fields']) || ! is_array($definition['fields'])) {
-            throw new IPF_ORM_Exception('No index columns given.');
-        }
-
-        $query = $type . 'INDEX ' . $name;
-
-        $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
-
-        return $query;
-    }
-
-    public function getIndexFieldDeclarationList(array $fields)
-    {
-        $ret = array();
-        foreach ($fields as $field => $definition) {
-            if (is_array($definition)) {
-                $ret[] = $this->conn->quoteIdentifier($field);
-            } else {
-                $ret[] = $this->conn->quoteIdentifier($definition);
-            }
-        }
-        return implode(', ', $ret);
-    }
-
-    public function getTemporaryTableQuery()
-    {
-        return 'TEMPORARY';
-    }
-
-    public function getForeignKeyDeclaration(array $definition)
-    {
-        $sql  = $this->getForeignKeyBaseDeclaration($definition);
-        $sql .= $this->getAdvancedForeignKeyOptions($definition);
-        return $sql;
-    }
-
-    public function getAdvancedForeignKeyOptions(array $definition)
-    {
-        $query = '';
-        if ( ! empty($definition['onUpdate'])) {
-            $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialAction($definition['onUpdate']);
-        }
-        if ( ! empty($definition['onDelete'])) {
-            $query .= ' ON DELETE ' . $this->getForeignKeyReferentialAction($definition['onDelete']);
-        }
-        return $query;
-    }
-
-    public function getForeignKeyReferentialAction($action)
-    {
-        $upper = strtoupper($action);
-        switch ($upper) {
-            case 'CASCADE':
-            case 'SET NULL':
-            case 'NO ACTION':
-            case 'RESTRICT':
-            case 'SET DEFAULT':
-                return $upper;
-            break;
-            default:
-                throw new IPF_ORM_Exception('Unknown foreign key referential action \'' . $upper . '\' given.');
-        }
-    }
-
-    public function getForeignKeyBaseDeclaration(array $definition)
-    {
-        $sql = '';
-        if (isset($definition['name'])) {
-            $sql .= ' CONSTRAINT ' . $this->conn->quoteIdentifier($definition['name']) . ' ';
-        }
-        $sql .= 'FOREIGN KEY (';
-
-        if ( ! isset($definition['local'])) {
-            throw new IPF_ORM_Exception('Local reference field missing from definition.');
-        }
-        if ( ! isset($definition['foreign'])) {
-            throw new IPF_ORM_Exception('Foreign reference field missing from definition.');
-        }
-        if ( ! isset($definition['foreignTable'])) {
-            throw new IPF_ORM_Exception('Foreign reference table missing from definition.');
-        }
-
-        if ( ! is_array($definition['local'])) {
-            $definition['local'] = array($definition['local']);
-        }
-        if ( ! is_array($definition['foreign'])) {
-            $definition['foreign'] = array($definition['foreign']);
-        }
-
-        $sql .= implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['local']))
-              . ') REFERENCES '
-              . $this->conn->quoteIdentifier($definition['foreignTable']) . '('
-              . implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['foreign'])) . ')';
-
-        return $sql;
-    }
-
-    public function exportSortedClassesSql($classes, $groupByConnection = true)
-    {
-         $connections = array();
-         foreach ($classes as $class) {
-             $connection = IPF_ORM_Manager::connection();
-             $connectionName = $connection->getName();
-
-             if ( ! isset($connections[$connectionName])) {
-                 $connections[$connectionName] = array(
-                     'create_tables'    => array(),
-                     'create_indexes'   => array(),
-                     'alters'           => array()
-                 );
-             }
-
-             $sql = $this->exportClassesSql($class);
-
-             // Build array of all the creates
-             // We need these to happen first
-             foreach ($sql as $key => $query) {
-                 // If create table statement
-                 if (substr($query, 0, strlen('CREATE TABLE')) == 'CREATE TABLE') {
-                     $connections[$connectionName]['create_tables'][] = $query;
-
-                     unset($sql[$key]);
-                     continue;
-                 }
-
-                 // If create index statement
-                 if (preg_grep("/CREATE .* INDEX/", array($query))) {
-                     $connections[$connectionName]['create_indexes'][] =  $query;
-
-                     unset($sql[$key]);
-                     continue;
-                 }
-
-                 // If alter table statement
-                 if (substr($query, 0, strlen('ALTER TABLE')) == 'ALTER TABLE') {
-                     $connections[$connectionName]['alters'][] = $query;
-
-                     unset($sql[$key]);
-                     continue;
-                 }
-             }
-         }
-
-         // Loop over all the sql again to merge everything together so it is in the correct order
-         $build = array();
-         foreach ($connections as $connectionName => $sql) {
-             $build[$connectionName] = array_merge($sql['create_tables'], $sql['create_indexes'], $sql['alters']);
-         }
-
-         if ( ! $groupByConnection) {
-             $new = array();
-             foreach($build as $connectionname => $sql) {
-                 $new = array_merge($new, $sql);
-             }
-             $build = $new;
-         }
-         return $build;
-    }
-
-     public function exportClasses(array $classes)
-     {
-         $queries = $this->exportSortedClassesSql($classes);
-         
-         foreach ($queries as $connectionName => $sql) {
-             $connection = IPF_ORM_Manager::connection();
-
-             $connection->beginTransaction();
-
-             foreach ($sql as $query) {
-                 try {
-                     $connection->exec($query);
-                 } catch (IPF_ORM_Exception $e) {
-                     // we only want to silence table already exists errors
-                     if ($e->getPortableCode() !== IPF_ORM::ERR_ALREADY_EXISTS) {
-                         $connection->rollback();
-                         throw new IPF_ORM_Exception($e->getMessage() . '. Failing Query: ' . $query);
-                     }
-                 }
-             }
-             $connection->commit();
-         }
-     }
-
-    public function exportClassesSql($name)
-    {
-        $sql = array();
-
-        $table  = IPF_ORM::getTable($name);
-
-        $data = $this->getExportableFormat($table);
-
-        $query = $this->createTableSql($data['tableName'], $data['columns'], $data['options']);
-
-        if (is_array($query)) {
-            $sql = array_merge($sql, $query);
-        } else {
-            $sql[] = $query;
-        }
-
-        $sql = array_unique($sql);
-        
-        rsort($sql);
-
-        return $sql;
-    }
-
-    private function getExportableFormat($table)
-    {
-        $columns = array();
-        $primary = array();
-
-        foreach ($table->getColumns() as $name => $definition) {
-
-            if (isset($definition['owner'])) {
-                continue;
-            }
-
-            switch ($definition['type']) {
-                case 'enum':
-                    if (isset($definition['default'])) {
-                        $definition['default'] = $table->enumIndex($name, $definition['default']);
-                    }
-                    break;
-                case 'boolean':
-                    if (isset($definition['default'])) {
-                        $definition['default'] = $table->getConnection()->convertBooleans($definition['default']);
-                    }
-                    break;
-            }
-            $columns[$name] = $definition;
-
-            if (isset($definition['primary']) && $definition['primary']) {
-                $primary[] = $name;
-            }
-        }
-
-        $options['foreignKeys'] = $table->getOption('foreignKeys', array());
-
-        if ($table->getAttribute(IPF_ORM::ATTR_EXPORT) & IPF_ORM::EXPORT_CONSTRAINTS) {
-            $constraints = array();
-
-            $emptyIntegrity = array('onUpdate' => null,
-                                    'onDelete' => null);
-
-            foreach ($table->getRelations() as $name => $relation) {
-                $fk = $relation->toArray();
-                $fk['foreignTable'] = $relation->getTable()->getTableName();
-
-                if ($relation->getTable() === $table && in_array($relation->getLocal(), $primary)) {
-                    if ($relation->hasConstraint()) {
-                        throw new IPF_ORM_Exception("Badly constructed integrity constraints.");
-                    }
-                    continue;
-                }
-
-                $integrity = array('onUpdate' => $fk['onUpdate'],
-                                   'onDelete' => $fk['onDelete']);
-
-                if ($relation instanceof IPF_ORM_Relation_LocalKey) {
-                    $def = array('local'        => $relation->getLocal(),
-                                 'foreign'      => $relation->getForeign(),
-                                 'foreignTable' => $relation->getTable()->getTableName());
-
-                    if (($key = array_search($def, $options['foreignKeys'])) === false) {
-                        $options['foreignKeys'][] = $def;
-                        $constraints[] = $integrity;
-                    } else {
-                        if ($integrity !== $emptyIntegrity) {
-                            $constraints[$key] = $integrity;
-                        }
-                    }
-                }
-            }
-
-            foreach ($constraints as $k => $def) {
-                $options['foreignKeys'][$k] = array_merge($options['foreignKeys'][$k], $def);
-            }
-        }
-
-        $options['primary'] = $primary;
-        
-        return array('tableName' => $table->getOption('tableName'),
-                     'columns'   => $columns,
-                     'options'   => array_merge($table->getOptions(), $options));
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/export/mysql.php b/ipf/legacy_orm/orm/export/mysql.php
deleted file mode 100644 (file)
index 1dbe90a..0000000
+++ /dev/null
@@ -1,493 +0,0 @@
-<?php
-
-class IPF_ORM_Export_Mysql extends IPF_ORM_Export
-{
-    public function createTableSql($name, array $fields, array $options = array()) 
-    {
-        if ( ! $name)
-            throw new IPF_ORM_Exception('no valid table name specified');
-
-        if (empty($fields)) {
-            throw new IPF_ORM_Exception('no fields specified for table "'.$name.'"');
-        }
-        $queryFields = $this->getFieldDeclarationList($fields);
-
-        // build indexes for all foreign key fields (needed in MySQL!!)
-        if (isset($options['foreignKeys'])) {
-            foreach ($options['foreignKeys'] as $fk) {
-                $local = $fk['local'];
-                $found = false;
-                if (isset($options['indexes'])) {
-                    foreach ($options['indexes'] as $definition) {
-                        if (is_string($definition['fields'])) {
-                            // Check if index already exists on the column                            
-                            $found = ($local == $definition['fields']);                        
-                        } else if (in_array($local, $definition['fields']) && count($definition['fields']) === 1) {
-                            // Index already exists on the column
-                            $found = true;
-                        }
-                    }
-                }
-                if (isset($options['primary']) && !empty($options['primary']) &&
-                        in_array($local, $options['primary'])) {
-                    // field is part of the PK and therefore already indexed
-                    $found = true;
-                }
-                
-                if ( ! $found) {
-                    if (is_array($local)) {
-                      foreach($local as $localidx) {
-                        $options['indexes'][$localidx] = array('fields' => array($localidx => array()));
-                      }
-                    } else {
-                      $options['indexes'][$local] = array('fields' => array($local => array()));                      
-                    }
-                }
-            }
-        }
-
-        // add all indexes
-        if (isset($options['indexes']) && ! empty($options['indexes'])) {
-            foreach($options['indexes'] as $index => $definition) {
-                $queryFields .= ', ' . $this->getIndexDeclaration($index, $definition);
-            }
-        }
-
-        // attach all primary keys
-        if (isset($options['primary']) && ! empty($options['primary'])) {
-            $keyColumns = array_values($options['primary']);
-            $keyColumns = array_map(array($this->conn, 'quoteIdentifier'), $keyColumns);
-            $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
-        }
-
-        $query = 'CREATE TABLE ' . $this->conn->quoteIdentifier($name) . ' (' . $queryFields . ')';
-
-        $optionStrings = array();
-
-        if (isset($options['comment'])) {
-            $optionStrings['comment'] = 'COMMENT = ' . $this->conn->quote($options['comment'], 'text');
-        }
-        if (isset($options['charset'])) {
-            $optionStrings['charset'] = 'DEFAULT CHARACTER SET ' . $options['charset'];
-        }
-        if (isset($options['collate'])) {
-            $optionStrings['collate'] = 'COLLATE ' . $options['collate'];
-        }
-
-        $type = false;
-
-        // get the type of the table
-        if (isset($options['type'])) {
-            $type = $options['type'];
-        } else {
-            $type = $this->conn->getAttribute(IPF_ORM::ATTR_DEFAULT_TABLE_TYPE);
-        }
-
-        if ($type) {
-            $optionStrings[] = 'ENGINE = ' . $type;
-        }
-
-        if ( ! empty($optionStrings)) {
-            $query.= ' '.implode(' ', $optionStrings);
-        }
-        $sql[] = $query;
-
-        if (isset($options['foreignKeys'])) {
-
-            foreach ((array) $options['foreignKeys'] as $k => $definition) {
-                if (is_array($definition)) {
-                    $sql[] = $this->createForeignKeySql($name, $definition);
-                }
-            }
-        }
-        return $sql;
-    }
-
-    public function getDeclaration($name, array $field)
-    {
-        $declaration = $this->conn->quoteIdentifier($name) . ' ';
-
-        if (!isset($field['type']))
-            throw new IPF_ORM_Exception('Missing column type.');
-
-        switch ($field['type']) {
-            case 'char':
-                $length = ( ! empty($field['length'])) ? $field['length'] : false;
-
-                $declaration .= $length ? 'CHAR('.$length.')' : 'CHAR(255)';
-                break;
-            case 'varchar':
-            case 'array':
-            case 'object':
-            case 'string':
-            case 'gzip':
-                if (!isset($field['length'])) {
-                    if (array_key_exists('default', $field)) {
-                        $field['length'] = $this->conn->varchar_max_length;
-                    } else {
-                        $field['length'] = false;
-                    }
-                }
-
-                $length = ($field['length'] <= $this->conn->varchar_max_length) ? $field['length'] : false;
-                $fixed  = (isset($field['fixed'])) ? $field['fixed'] : false;
-
-                $declaration .= $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
-                    : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
-                break;
-            case 'clob':
-                if (!empty($field['length'])) {
-                    $length = $field['length'];
-                    if ($length <= 255) {
-                        return 'TINYTEXT';
-                    } elseif ($length <= 65532) {
-                        return 'TEXT';
-                    } elseif ($length <= 16777215) {
-                        return 'MEDIUMTEXT';
-                    }
-                }
-                $declaration .= 'LONGTEXT';
-                break;
-            case 'blob':
-                if ( ! empty($field['length'])) {
-                    $length = $field['length'];
-                    if ($length <= 255) {
-                        return 'TINYBLOB';
-                    } elseif ($length <= 65532) {
-                        return 'BLOB';
-                    } elseif ($length <= 16777215) {
-                        return 'MEDIUMBLOB';
-                    }
-                }
-                $declaration .= 'LONGBLOB';
-                break;
-            case 'enum':
-                if ($this->conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
-                    $values = array();
-                    foreach ($field['values'] as $value) {
-                      $values[] = $this->conn->quote($value, 'varchar');
-                    }
-                    $declaration .= 'ENUM('.implode(', ', $values).')';
-                    break;
-                }
-                // fall back to integer
-            case 'integer':
-            case 'int':
-                $type = 'INT';
-                if (!empty($field['length'])) {
-                    $length = $field['length'];
-                    if ($length <= 1) {
-                        $type = 'TINYINT';
-                    } elseif ($length == 2) {
-                        $type = 'SMALLINT';
-                    } elseif ($length == 3) {
-                        $type = 'MEDIUMINT';
-                    } elseif ($length == 4) {
-                        $type = 'INT';
-                    } elseif ($length > 4) {
-                        $type = 'BIGINT';
-                    }
-                }
-                $declaration .= $type;
-                if (isset($field['unsigned']) && $field['unsigned'])
-                    $declaration .= ' UNSIGNED';
-                break;
-            case 'boolean':
-                $declaration .= 'TINYINT(1)';
-                break;
-            case 'date':
-                $declaration .= 'DATE';
-                break;
-            case 'time':
-                $declaration .= 'TIME';
-                break;
-            case 'datetime':
-                $declaration .= 'DATETIME';
-                break;
-            case 'timestamp':
-                $declaration .= 'TIMESTAMP';
-                break;
-            case 'float':
-            case 'double':
-                $declaration .= 'DOUBLE';
-                break;
-            case 'decimal':
-                $scale = !empty($field['scale']) ? $field['scale'] : $this->conn->getAttribute(IPF_ORM::ATTR_DECIMAL_PLACES);
-                if (!empty($field['length'])) {
-                    $length = $field['length'];
-                    if (is_array($length)) {
-                        list($length, $scale) = $length;
-                    }
-                } else {
-                    $length = 18;
-                }
-                $declaration .= 'DECIMAL('.$length.','.$scale.')';
-                break;
-            case 'bit':
-                $declaration .= 'BIT';
-                break;
-            default:
-                throw new IPF_ORM_Exception('Unknown field type \'' . $field['type'] .  '\'.');
-        }
-
-        if (isset($field['charset']) && $field['charset'])
-            $declaration .= ' CHARACTER SET ' . $field['charset'];
-
-        if (isset($field['collate']) && $field['collate'])
-            $declaration .= ' COLLATE ' . $field['collate'];
-
-        if (isset($field['notnull']) && $field['notnull'])
-            $declaration .= ' NOT NULL';
-
-        if (!empty($field['autoincrement'])) {
-            $declaration .= ' AUTO_INCREMENT';
-        } else {
-            $declaration .= $this->getDefaultFieldDeclaration($field);
-
-            if (isset($field['unique']) && $field['unique'])
-                $declaration .= ' UNIQUE';
-        }
-
-        if (isset($field['comment']) && $field['comment'])
-            $declaration .= ' COMMENT ' . $this->conn->quote($field['comment'], 'varchar');
-
-        return $declaration;
-    }
-
-    private function getDefaultFieldDeclaration($field)
-    {
-        $default = '';
-        if (isset($field['default']) && (!isset($field['length']) || $field['length'] <= 255)) {
-            if ($field['default'] === '') {
-                $field['default'] = empty($field['notnull']) ? null : $this->valid_default_values[$field['type']];
-            }
-
-            if ($field['default'] === '' && ($this->conn->getAttribute(IPF_ORM::ATTR_PORTABILITY) & IPF_ORM::PORTABILITY_EMPTY_TO_NULL))
-                $field['default'] = null;
-
-            if (is_null($field['default'])) {
-                $default = ' DEFAULT NULL';
-            } else {
-                if ($field['type'] === 'boolean') {
-                    $fieldType = 'boolean';
-                    $field['default'] = $this->conn->convertBooleans($field['default']);
-                } elseif ($field['type'] == 'enum' && $this->conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
-                    $fieldType = 'varchar';
-                } else {
-                    $fieldType = $field['type'];
-                }
-                $default = ' DEFAULT ' . $this->conn->quote($field['default'], $fieldType);
-            }
-        }
-        return $default;
-    }
-
-    public function alterTableSql($name, array $changes, $check = false)
-    {
-        if ( ! $name) {
-            throw new IPF_ORM_Exception('no valid table name specified');
-        }
-        foreach ($changes as $changeName => $change) {
-            switch ($changeName) {
-                case 'add':
-                case 'remove':
-                case 'change':
-                case 'rename':
-                case 'name':
-                    break;
-                default:
-                    throw new IPF_ORM_Exception('change type "' . $changeName . '" not yet supported');
-            }
-        }
-
-        if ($check) {
-            return true;
-        }
-
-        $query = '';
-        if ( ! empty($changes['name'])) {
-            $change_name = $this->conn->quoteIdentifier($changes['name']);
-            $query .= 'RENAME TO ' . $change_name;
-        }
-
-        if ( ! empty($changes['add']) && is_array($changes['add'])) {
-            foreach ($changes['add'] as $fieldName => $field) {
-                if ($query) {
-                    $query.= ', ';
-                }
-                $query.= 'ADD ' . $this->getDeclaration($fieldName, $field);
-            }
-        }
-
-        if ( ! empty($changes['remove']) && is_array($changes['remove'])) {
-            foreach ($changes['remove'] as $fieldName => $field) {
-                if ($query) {
-                    $query .= ', ';
-                }
-                $fieldName = $this->conn->quoteIdentifier($fieldName);
-                $query .= 'DROP ' . $fieldName;
-            }
-        }
-
-        $rename = array();
-        if ( ! empty($changes['rename']) && is_array($changes['rename'])) {
-            foreach ($changes['rename'] as $fieldName => $field) {
-                $rename[$field['name']] = $fieldName;
-            }
-        }
-
-        if ( ! empty($changes['change']) && is_array($changes['change'])) {
-            foreach ($changes['change'] as $fieldName => $field) {
-                if ($query) {
-                    $query.= ', ';
-                }
-                if (isset($rename[$fieldName])) {
-                    $oldFieldName = $rename[$fieldName];
-                    unset($rename[$fieldName]);
-                } else {
-                    $oldFieldName = $fieldName;
-                }
-                $oldFieldName = $this->conn->quoteIdentifier($oldFieldName);
-                $query .= 'CHANGE ' . $oldFieldName . ' ' 
-                        . $this->getDeclaration($fieldName, $field['definition']);
-            }
-        }
-
-        if ( ! empty($rename) && is_array($rename)) {
-            foreach ($rename as $renameName => $renamedField) {
-                if ($query) {
-                    $query.= ', ';
-                }
-                $field = $changes['rename'][$renamedField];
-                $renamedField = $this->conn->quoteIdentifier($renamedField);
-                $query .= 'CHANGE ' . $renamedField . ' '
-                        . $this->getDeclaration($field['name'], $field['definition']);
-            }
-        }
-
-        if ( ! $query) {
-            return false;
-        }
-
-        $name = $this->conn->quoteIdentifier($name);
-        
-        return 'ALTER TABLE ' . $name . ' ' . $query;
-    }
-
-    public function createIndexSql($table, $name, array $definition)
-    {
-        $name   = $this->conn->quoteIdentifier($this->getIndexName($name));
-        $type   = '';
-        if (isset($definition['type'])) {
-            switch (strtolower($definition['type'])) {
-                case 'fulltext':
-                case 'unique':
-                    $type = strtoupper($definition['type']) . ' ';
-                break;
-                default:
-                    throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']);
-            }
-        }
-        $query  = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table;
-        $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
-
-        return $query;
-    }
-
-    public function getIndexDeclaration($name, array $definition)
-    {
-        $name   = $this->getIndexName($name);
-        $type   = '';
-        if (isset($definition['type'])) {
-            switch (strtolower($definition['type'])) {
-                case 'fulltext':
-                case 'unique':
-                    $type = strtoupper($definition['type']) . ' ';
-                break;
-                default:
-                    throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']);
-            }
-        }
-        
-        if ( ! isset($definition['fields'])) {
-            throw new IPF_ORM_Exception('No index columns given.');
-        }
-        if ( ! is_array($definition['fields'])) {
-            $definition['fields'] = array($definition['fields']);
-        }
-
-        $query = $type . 'INDEX ' . $this->conn->quoteIdentifier($name);
-
-        $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
-        
-        return $query;
-    }
-
-    public function getIndexFieldDeclarationList(array $fields)
-    {
-        $declFields = array();
-
-        foreach ($fields as $fieldName => $field) {
-            $fieldString = $this->conn->quoteIdentifier($fieldName);
-
-            if (is_array($field)) {
-                if (isset($field['length'])) {
-                    $fieldString .= '(' . $field['length'] . ')';
-                }
-
-                if (isset($field['sorting'])) {
-                    $sort = strtoupper($field['sorting']);
-                    switch ($sort) {
-                        case 'ASC':
-                        case 'DESC':
-                            $fieldString .= ' ' . $sort;
-                            break;
-                        default:
-                            throw new IPF_ORM_Exception('Unknown index sorting option given.');
-                    }
-                }
-            } else {
-                $fieldString = $this->conn->quoteIdentifier($field);
-            }
-            $declFields[] = $fieldString;
-        }
-        return implode(', ', $declFields);
-    }
-
-    public function getAdvancedForeignKeyOptions(array $definition)
-    {
-        $query = '';
-        if ( ! empty($definition['match'])) {
-            $query .= ' MATCH ' . $definition['match'];
-        }
-        if ( ! empty($definition['onUpdate'])) {
-            $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialAction($definition['onUpdate']);
-        }
-        if ( ! empty($definition['onDelete'])) {
-            $query .= ' ON DELETE ' . $this->getForeignKeyReferentialAction($definition['onDelete']);
-        }
-        return $query;
-    }
-
-    public function dropIndexSql($table, $name)
-    {
-        $table  = $this->conn->quoteIdentifier($table);
-        $name   = $this->conn->quoteIdentifier($this->getIndexName($name));
-        return 'DROP INDEX ' . $name . ' ON ' . $table;
-    }
-
-    public function dropTableSql($table)
-    {
-        $table  = $this->conn->quoteIdentifier($table);
-        return 'DROP TABLE ' . $table;
-    }
-
-    public function dropForeignKey($table, $name)
-    {
-        $table = $this->conn->quoteIdentifier($table);
-        $name  = $this->conn->quoteIdentifier($name);
-
-        return $this->conn->exec('ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $name);
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/expression.php b/ipf/legacy_orm/orm/expression.php
deleted file mode 100644 (file)
index a866412..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php
-
-class IPF_ORM_Expression
-{
-    protected $_expression;
-    protected $_conn;
-    protected $_tokenizer;
-
-    public function __construct($expr, $conn = null)
-    {
-        $this->_tokenizer = new IPF_ORM_Query_Tokenizer();
-        $this->setExpression($expr);
-        if ($conn !== null) {
-            $this->_conn = $conn;
-        }
-    }
-
-    public function getConnection()
-    {
-        if ( ! isset($this->_conn)) {
-            return IPF_ORM_Manager::connection();
-        }
-
-        return $this->_conn;
-    }
-
-    public function setExpression($clause)
-    {
-        $this->_expression = $this->parseClause($clause);
-    }
-
-    public function parseExpression($expr)
-    {
-        $pos  = strpos($expr, '(');
-        $quoted = (substr($expr, 0, 1) === "'" && substr($expr, -1) === "'");
-        if ($pos === false || $quoted) {
-            return $expr;
-        }
-
-        // get the name of the function
-        $name   = substr($expr, 0, $pos);
-        $argStr = substr($expr, ($pos + 1), -1);
-
-        // parse args
-        foreach ($this->_tokenizer->bracketExplode($argStr, ',') as $arg) {
-           $args[] = $this->parseClause($arg);
-        }
-
-        return call_user_func_array(array($this->getConnection()->expression, $name), $args);
-    }
-
-    public function parseClause($clause)
-    {
-        $e = $this->_tokenizer->bracketExplode($clause, ' ');
-
-        foreach ($e as $k => $expr) {
-            $e[$k] = $this->parseExpression($expr);
-        }
-        
-        return implode(' ', $e);
-    }
-
-    public function getSql()
-    {
-        return $this->_expression;
-    }
-
-    public function __toString()
-    {
-        return $this->getSql();
-    }
-}
diff --git a/ipf/legacy_orm/orm/expression/driver.php b/ipf/legacy_orm/orm/expression/driver.php
deleted file mode 100644 (file)
index 5c7baa2..0000000
+++ /dev/null
@@ -1,288 +0,0 @@
-<?php
-
-class IPF_ORM_Expression_Driver extends IPF_ORM_Connection_Module
-{
-    public function getIdentifier($column)
-    {
-        return $column;
-    }
-    public function getIdentifiers($columns)
-    {
-        return $columns;
-    }
-
-    public function regexp()
-    {
-        throw new IPF_ORM_Exception('Regular expression operator is not supported by this database driver.');
-    }
-
-    public function avg($column)
-    {
-        $column = $this->getIdentifier($column);
-        return 'AVG(' .  $column . ')';
-    }
-
-    public function count($column)
-    {
-        $column = $this->getIdentifier($column);
-        return 'COUNT(' . $column . ')';
-    }
-
-    public function max($column)
-    {
-        $column = $this->getIdentifier($column);
-        return 'MAX(' . $column . ')';
-    }
-
-    public function min($column)
-    {
-        $column = $this->getIdentifier($column);
-        return 'MIN(' . $column . ')';
-    }
-
-    public function sum($column)
-    {
-        $column = $this->getIdentifier($column);
-        return 'SUM(' . $column . ')';
-    }
-
-    public function md5($column)
-    {
-        $column = $this->getIdentifier($column);
-        return 'MD5(' . $column . ')';
-    }
-
-    public function length($column)
-    {
-        $column = $this->getIdentifier($column);
-        return 'LENGTH(' . $column . ')';
-    }
-
-    public function round($column, $decimals = 0)
-    {
-        $column = $this->getIdentifier($column);
-
-        return 'ROUND(' . $column . ', ' . $decimals . ')';
-    }
-
-    public function mod($expression1, $expression2)
-    {
-        $expression1 = $this->getIdentifier($expression1);
-        $expression2 = $this->getIdentifier($expression2);
-        return 'MOD(' . $expression1 . ', ' . $expression2 . ')';
-    }
-
-    public function trim($str)
-    {
-        return 'TRIM(' . $str . ')';
-    }
-
-    public function rtrim($str)
-    {
-        return 'RTRIM(' . $str . ')';
-    }
-
-    public function ltrim($str)
-    {
-        return 'LTRIM(' . $str . ')';
-    }
-
-    public function upper($str)
-    {
-        return 'UPPER(' . $str . ')';
-    }
-
-    public function lower($str)
-    {
-        return 'LOWER(' . $str . ')';
-    }
-
-    public function locate($str, $substr)
-    {
-        return 'LOCATE(' . $str . ', ' . $substr . ')';
-    }
-
-    public function now()
-    {
-        return 'NOW()';
-    }
-
-    public function coalesce($expression1, $expression2)
-    {
-        $expression1 = $this->getIdentifier($expression1);
-        $expression2 = $this->getIdentifier($expression2);
-        return 'COALESCE(' . $expression1 . ', ' . $expression2 . ')';
-    }
-
-    public function soundex($value)
-    {
-        throw new IPF_ORM_Exception('SQL soundex function not supported by this driver.');
-    }
-
-    public function substring($value, $from, $len = null)
-    {
-        $value = $this->getIdentifier($value);
-        if ($len === null)
-            return 'SUBSTRING(' . $value . ' FROM ' . $from . ')';
-        else {
-            $len = $this->getIdentifier($len);
-            return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $len . ')';
-        }
-    }
-
-    public function concat()
-    {
-        $args = func_get_args();
-
-        return 'CONCAT(' . join(', ', (array) $args) . ')';
-    }
-
-    public function not($expression)
-    {
-        $expression = $this->getIdentifier($expression);
-        return 'NOT(' . $expression . ')';
-    }
-
-    private function basicMath($type, array $args)
-    {
-        $elements = $this->getIdentifiers($args);
-        if (count($elements) < 1) {
-            return '';
-        }
-        if (count($elements) == 1) {
-            return $elements[0];
-        } else {
-            return '(' . implode(' ' . $type . ' ', $elements) . ')';
-        }
-    }
-
-    public function add(array $args)
-    {
-        return $this->basicMath('+', $args);
-    }
-
-    public function sub(array $args)
-    {
-        return $this->basicMath('-', $args );
-    }
-
-    public function mul(array $args)
-    {
-        return $this->basicMath('*', $args);
-    }
-
-    public function div(array $args)
-    {
-        return $this->basicMath('/', $args);
-    }
-
-    public function eq($value1, $value2)
-    {
-        $value1 = $this->getIdentifier($value1);
-        $value2 = $this->getIdentifier($value2);
-        return $value1 . ' = ' . $value2;
-    }
-
-    public function neq($value1, $value2)
-    {
-        $value1 = $this->getIdentifier($value1);
-        $value2 = $this->getIdentifier($value2);
-        return $value1 . ' <> ' . $value2;
-    }
-
-    public function gt($value1, $value2)
-    {
-        $value1 = $this->getIdentifier($value1);
-        $value2 = $this->getIdentifier($value2);
-        return $value1 . ' > ' . $value2;
-    }
-
-    public function gte($value1, $value2)
-    {
-        $value1 = $this->getIdentifier($value1);
-        $value2 = $this->getIdentifier($value2);
-        return $value1 . ' >= ' . $value2;
-    }
-
-    public function lt($value1, $value2)
-    {
-        $value1 = $this->getIdentifier($value1);
-        $value2 = $this->getIdentifier($value2);
-        return $value1 . ' < ' . $value2;
-    }
-
-    public function lte($value1, $value2)
-    {
-        $value1 = $this->getIdentifier($value1);
-        $value2 = $this->getIdentifier($value2);
-        return $value1 . ' <= ' . $value2;
-    }
-
-    public function in($column, $values)
-    {
-        if ( ! is_array($values)) {
-            $values = array($values);
-        }
-        $values = $this->getIdentifiers($values);
-        $column = $this->getIdentifier($column);
-
-        if (count($values) == 0) {
-            throw new IPF_ORM_Exception('Values array for IN operator should not be empty.');
-        }
-        return $column . ' IN (' . implode(', ', $values) . ')';
-    }
-
-    public function isNull($expression)
-    {
-        $expression = $this->getIdentifier($expression);
-        return $expression . ' IS NULL';
-    }
-
-    public function isNotNull($expression)
-    {
-        $expression = $this->getIdentifier($expression);
-        return $expression . ' IS NOT NULL';
-    }
-
-    public function between($expression, $value1, $value2)
-    {
-        $expression = $this->getIdentifier($expression);
-        $value1 = $this->getIdentifier($value1);
-        $value2 = $this->getIdentifier($value2);
-        return $expression . ' BETWEEN ' .$value1 . ' AND ' . $value2;
-    }
-
-    public function guid()
-    {
-        throw new IPF_ORM_Exception('method not implemented');
-    }
-
-    public function acos($value)
-    {
-        return 'ACOS(' . $value . ')';
-    }
-
-    public function sin($value)
-    {
-        return 'SIN(' . $value . ')';
-    }
-
-    public function pi()
-    {
-        return 'PI()';
-    }
-
-    public function cos($value)
-    {
-        return 'COS(' . $value . ')';
-    }
-
-    public function __call($m, $a) 
-    {
-        if ($this->conn->getAttribute(IPF_ORM::ATTR_PORTABILITY) & IPF_ORM::PORTABILITY_EXPR) {
-            throw new IPF_ORM_Exception('Unknown expression ' . $m);
-        }
-        return $m . '(' . implode(', ', $a) . ')';
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/expression/mysql.php b/ipf/legacy_orm/orm/expression/mysql.php
deleted file mode 100644 (file)
index cc11fc6..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-class IPF_ORM_Expression_Mysql extends IPF_ORM_Expression_Driver
-{
-    public function regexp()
-    {
-        return 'RLIKE';
-    }
-
-    public function random()
-    {
-        return 'RAND()';
-    }
-
-    public function guid()
-    {
-        return 'UUID()';
-    }
-
-    public function year($column)
-    {
-        $column = $this->getIdentifier($column);
-        return 'YEAR(' .  $column . ')';
-    }
-
-    public function month($column)
-    {
-        $column = $this->getIdentifier($column);
-        return 'MONTH(' .  $column . ')';
-    }
-
-    public function monthname($column)
-    {
-        $column = $this->getIdentifier($column);
-        return 'MONTHNAME(' .  $column . ')';
-    }
-
-    public function day($column)
-    {
-        $column = $this->getIdentifier($column);
-        return 'DAY(' .  $column . ')';
-    }
-}
diff --git a/ipf/legacy_orm/orm/hydrator.php b/ipf/legacy_orm/orm/hydrator.php
deleted file mode 100644 (file)
index a4bb6bf..0000000
+++ /dev/null
@@ -1,271 +0,0 @@
-<?php
-
-class IPF_ORM_Hydrator extends IPF_ORM_Hydrator_Abstract
-{
-    public function hydrateResultSet($stmt, $tableAliases)
-    {
-        $hydrationMode = $this->_hydrationMode;
-
-        $this->_tableAliases = $tableAliases;
-
-        if ($hydrationMode == IPF_ORM::HYDRATE_NONE) {
-            return $stmt->fetchAll(PDO::FETCH_NUM);
-        }
-
-        if ($hydrationMode == IPF_ORM::HYDRATE_ARRAY) {
-            $driver = new IPF_ORM_Hydrator_ArrayDriver();
-        } else {
-            $driver = new IPF_ORM_Hydrator_RecordDriver();
-        }
-
-        // Used variables during hydration
-        reset($this->_queryComponents);
-        $rootAlias = key($this->_queryComponents);
-        $rootComponentName = $this->_queryComponents[$rootAlias]['table']->getComponentName();
-        // if only one component is involved we can make our lives easier
-        $isSimpleQuery = count($this->_queryComponents) <= 1;
-        // Holds the resulting hydrated data structure
-        $result = array();
-        // Lookup map to quickly discover/lookup existing records in the result
-        $identifierMap = array();
-        // Holds for each component the last previously seen element in the result set
-        $prev = array();
-        // holds the values of the identifier/primary key fields of components,
-        // separated by a pipe '|' and grouped by component alias (r, u, i, ... whatever)
-        // the $idTemplate is a prepared template. $id is set to a fresh template when
-        // starting to process a row.
-        $id = array();
-        $idTemplate = array();
-
-        $result = $driver->getElementCollection($rootComponentName);
-
-        if ($stmt === false || $stmt === 0) {
-            return $result;
-        }
-
-        // Initialize
-        foreach ($this->_queryComponents as $dqlAlias => $data) {
-            $componentName = $data['table']->getComponentName();
-            $identifierMap[$dqlAlias] = array();
-            $prev[$dqlAlias] = null;
-            $idTemplate[$dqlAlias] = '';
-        }
-
-        $event = new IPF_ORM_Event(null, IPF_ORM_Event::HYDRATE, null);
-
-        // Process result set
-        $cache = array();
-        while ($data = $stmt->fetch(IPF_ORM::FETCH_ASSOC)) {
-            $id = $idTemplate; // initialize the id-memory
-            $nonemptyComponents = array();
-            $rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents);
-
-            //
-            // hydrate the data of the root component from the current row
-            //
-            $table = $this->_queryComponents[$rootAlias]['table'];
-            $componentName = $table->getComponentName();
-            // Ticket #1115 (getInvoker() should return the component that has addEventListener)
-            $event->setInvoker($table);
-            $event->set('data', $rowData[$rootAlias]);
-            $table->notifyRecordListeners('preHydrate', $event);
-
-            $index = false;
-
-            // Check for an existing element
-            if ($isSimpleQuery || ! isset($identifierMap[$rootAlias][$id[$rootAlias]])) {
-                $element = $driver->getElement($rowData[$rootAlias], $componentName);
-                $event->set('data', $element);
-                $table->notifyRecordListeners('postHydrate', $event);
-
-                // do we need to index by a custom field?
-                if ($field = $this->_getCustomIndexField($rootAlias)) {
-                    if (isset($result[$field])) {
-                        throw new IPF_ORM_Exception("Couldn't hydrate. Found non-unique key mapping.");
-                    } else if ( ! isset($element[$field])) {
-                        throw new IPF_ORM_Exception("Couldn't hydrate. Found a non-existent key.");
-                    }
-                    $result[$element[$field]] = $element;
-                } else {
-                    $result[] = $element;
-                }
-
-                $identifierMap[$rootAlias][$id[$rootAlias]] = $driver->getLastKey($result);
-            } else {
-                $index = $identifierMap[$rootAlias][$id[$rootAlias]];
-            }
-
-            $this->_setLastElement($prev, $result, $index, $rootAlias, false);
-            unset($rowData[$rootAlias]);
-
-            // end hydrate data of the root component for the current row
-
-
-            // $prev[$rootAlias] now points to the last element in $result.
-            // now hydrate the rest of the data found in the current row, that belongs to other
-            // (related) components.
-            foreach ($rowData as $dqlAlias => $data) {
-                $index = false;
-                $map = $this->_queryComponents[$dqlAlias];
-                $table = $map['table'];
-                $componentName = $table->getComponentName();
-                $event->set('data', $data);
-                $table->notifyRecordListeners('preHydrate', $event);
-
-                $parent = $map['parent'];
-                $relation = $map['relation'];
-                $relationAlias = $map['relation']->getAlias();
-
-                $path = $parent . '.' . $dqlAlias;
-
-                if ( ! isset($prev[$parent])) {
-                    unset($prev[$dqlAlias]); // Ticket #1228
-                    continue;
-                }
-
-                // check the type of the relation
-                if ( ! $relation->isOneToOne() && $driver->initRelated($prev[$parent], $relationAlias)) {
-                    $oneToOne = false;
-                    // append element
-                    if (isset($nonemptyComponents[$dqlAlias])) {
-                        $indexExists = isset($identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
-                        $index = $indexExists ? $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
-                        $indexIsValid = $index !== false ? isset($prev[$parent][$relationAlias][$index]) : false;
-                        if ( ! $indexExists || ! $indexIsValid) {
-                            $element = $driver->getElement($data, $componentName);
-                            $event->set('data', $element);
-                            $table->notifyRecordListeners('postHydrate', $event);
-
-                            if ($field = $this->_getCustomIndexField($dqlAlias)) {
-                                if (isset($prev[$parent][$relationAlias][$element[$field]])) {
-                                    throw new IPF_ORM_Exception("Couldn't hydrate. Found non-unique key mapping.");
-                                } else if ( ! isset($element[$field])) {
-                                    throw new IPF_ORM_Exception("Couldn't hydrate. Found a non-existent key.");
-                                }
-                                $prev[$parent][$relationAlias][$element[$field]] = $element;
-                            } else {
-                                $prev[$parent][$relationAlias][] = $element; 
-                            }
-                            $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $driver->getLastKey($prev[$parent][$relationAlias]);                            
-                        }
-                        // register collection for later snapshots
-                        $driver->registerCollection($prev[$parent][$relationAlias]);
-                    }
-                } else {
-                    // 1-1 relation
-                    $oneToOne = true;
-
-                    if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($prev[$parent][$relationAlias])) {
-                        $prev[$parent][$relationAlias] = $driver->getNullPointer();
-                    } else if ( ! isset($prev[$parent][$relationAlias])) {
-                        $element = $driver->getElement($data, $componentName);
-
-                                               // [FIX] Tickets #1205 and #1237
-                        $event->set('data', $element);
-                        $table->notifyRecordListeners('postHydrate', $event);
-
-                        $prev[$parent][$relationAlias] = $element;
-                    }
-                }
-                
-                $coll =& $prev[$parent][$relationAlias];
-                $this->_setLastElement($prev, $coll, $index, $dqlAlias, $oneToOne);
-            }
-        }
-
-        $stmt->closeCursor();
-        $driver->flush();
-        //$e = microtime(true);
-        //echo 'Hydration took: ' . ($e - $s) . ' for '.count($result).' records<br />';
-
-        return $result;
-    }
-
-    protected function _setLastElement(&$prev, &$coll, $index, $dqlAlias, $oneToOne)
-    {
-        if (IPF_ORM_Null::isNull($coll) || $coll === null) {
-            unset($prev[$dqlAlias]); // Ticket #1228
-            return;
-        }
-
-        if ($index !== false) {
-            // Link element at $index to previous element for the component
-            // identified by the DQL alias $alias
-            $prev[$dqlAlias] =& $coll[$index];
-            return;
-        }
-
-        if (is_array($coll) && $coll) {
-            if ($oneToOne) {
-                $prev[$dqlAlias] =& $coll;
-            } else {
-                end($coll);
-                $prev[$dqlAlias] =& $coll[key($coll)];
-            }
-        } else if (count($coll) > 0) {
-            $prev[$dqlAlias] = $coll->getLast();
-        }
-    }
-
-    protected function _gatherRowData(&$data, &$cache, &$id, &$nonemptyComponents)
-    {
-        $rowData = array();
-
-        foreach ($data as $key => $value) {
-            // Parse each column name only once. Cache the results. 
-            if ( ! isset($cache[$key])) {
-                // check ignored names. fastest solution for now. if we get more we'll start
-                // to introduce a list.
-                if ($key == 'IPF_ORM_ROWNUM') continue;
-                $e = explode('__', $key);
-                $last = strtolower(array_pop($e));
-                $cache[$key]['dqlAlias'] = $this->_tableAliases[strtolower(implode('__', $e))];
-                $table = $this->_queryComponents[$cache[$key]['dqlAlias']]['table'];
-                $fieldName = $table->getFieldName($last);
-                $cache[$key]['fieldName'] = $fieldName;
-                if ($table->isIdentifier($fieldName)) {
-                    $cache[$key]['isIdentifier'] = true;
-                } else {
-                  $cache[$key]['isIdentifier'] = false;
-                }
-                $type = $table->getTypeOfColumn($last);
-                if ($type == 'integer' || $type == 'string') {
-                    $cache[$key]['isSimpleType'] = true;
-                } else {
-                    $cache[$key]['type'] = $type;
-                    $cache[$key]['isSimpleType'] = false;
-                }
-            }
-
-            $map = $this->_queryComponents[$cache[$key]['dqlAlias']];
-            $table = $map['table'];
-            $dqlAlias = $cache[$key]['dqlAlias'];
-            $fieldName = $cache[$key]['fieldName'];
-            if (isset($this->_queryComponents[$dqlAlias]['agg'][$fieldName])) {
-                $fieldName = $this->_queryComponents[$dqlAlias]['agg'][$fieldName];
-            }
-
-            if ($cache[$key]['isIdentifier']) {
-                $id[$dqlAlias] .= '|' . $value;
-            }
-
-            if ($cache[$key]['isSimpleType']) {
-                $rowData[$dqlAlias][$fieldName] = $value;
-            } else {
-                $rowData[$dqlAlias][$fieldName] = $table->prepareValue(
-                        $fieldName, $value, $cache[$key]['type']);
-            }
-
-            if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) {
-                $nonemptyComponents[$dqlAlias] = true;
-            }
-        }
-
-        return $rowData;
-    }
-
-    protected function _getCustomIndexField($alias)
-    {
-        return isset($this->_queryComponents[$alias]['map']) ? $this->_queryComponents[$alias]['map'] : null;
-    }
-}
diff --git a/ipf/legacy_orm/orm/hydrator/abstract.php b/ipf/legacy_orm/orm/hydrator/abstract.php
deleted file mode 100644 (file)
index 497d2ca..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-abstract class IPF_ORM_Hydrator_Abstract
-{
-    protected $_queryComponents = array();
-
-    protected $_hydrationMode = IPF_ORM::HYDRATE_RECORD;
-
-    public function __construct() {}
-
-    public function setHydrationMode($hydrationMode)
-    {
-        $this->_hydrationMode = $hydrationMode;
-    }
-
-    public function getHydrationMode()
-    {
-        return $this->_hydrationMode;
-    }
-
-    public function setQueryComponents(array $queryComponents)
-    {
-        $this->_queryComponents = $queryComponents;
-    }
-
-    public function getQueryComponents()
-    {
-        return $this->_queryComponents;
-    }
-
-    abstract public function hydrateResultSet($stmt, $tableAliases);
-}
diff --git a/ipf/legacy_orm/orm/hydrator/arraydriver.php b/ipf/legacy_orm/orm/hydrator/arraydriver.php
deleted file mode 100644 (file)
index 6018d62..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-class IPF_ORM_Hydrator_ArrayDriver
-{
-    public function getElementCollection($component)
-    {
-        return array();
-    }
-    public function getElement(array $data, $component)
-    {
-        return $data;
-    }
-
-    public function registerCollection($coll)
-    {
-    }
-
-    public function initRelated(array &$data, $name)
-    {
-        if ( ! isset($data[$name])) {
-            $data[$name] = array();
-        }
-        return true;
-    }
-
-    public function getNullPointer() 
-    {
-        return null;    
-    }
-
-    public function getLastKey(&$data)
-    {
-        end($data);
-        return key($data);
-    }
-
-    public function flush()
-    {
-    }
-}
diff --git a/ipf/legacy_orm/orm/hydrator/recorddriver.php b/ipf/legacy_orm/orm/hydrator/recorddriver.php
deleted file mode 100644 (file)
index df7f498..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-<?php
-
-class IPF_ORM_Hydrator_RecordDriver
-{
-    protected $_collections = array();
-    protected $_tables = array();
-    private $_initializedRelations = array();
-
-    public function getElementCollection($component)
-    {
-        $coll = new IPF_ORM_Collection($component);
-        $this->_collections[] = $coll;
-
-        return $coll;
-    }
-
-    public function getLastKey($coll) 
-    {
-        $coll->end();
-        
-        return $coll->key();
-    }
-    
-    public function initRelated(IPF_ORM_Record $record, $name)
-    {
-        if ( ! isset($this->_initializedRelations[$record->getOid()][$name])) {
-            $relation = $record->getTable()->getRelation($name);
-            $coll = new IPF_ORM_Collection($relation->getTable()->getComponentName());
-            $coll->setReference($record, $relation);
-            $record[$name] = $coll;
-            $this->_initializedRelations[$record->getOid()][$name] = true;
-        }
-        return true;
-    }
-    
-    public function registerCollection(IPF_ORM_Collection $coll)
-    {
-        $this->_collections[] = $coll;
-    }
-    
-    public function getNullPointer()
-    {
-        return IPF_ORM_Null::getInstance();
-    }
-    
-    public function getElement(array $data, $component)
-    {
-        $component = $this->_getClassNameToReturn($data, $component);
-        if ( ! isset($this->_tables[$component])) {
-            $this->_tables[$component] = IPF_ORM::getTable($component);
-            $this->_tables[$component]->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, false);
-        }
-
-        $this->_tables[$component]->setData($data);
-        $record = $this->_tables[$component]->getRecord();
-
-        return $record;
-    }
-    
-    public function flush()
-    {
-        // take snapshots from all initialized collections
-        foreach ($this->_collections as $key => $coll) {
-            $coll->takeSnapshot();
-        }
-        foreach ($this->_tables as $table) {
-            $table->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, true);
-        }
-        $this->_initializedRelations = null;
-        $this->_collections = null;
-        $this->_tables = null;
-    }
-    
-    protected function _getClassNameToReturn(array &$data, $component)
-    {
-        if ( ! isset($this->_tables[$component])) {
-            $this->_tables[$component] = IPF_ORM::getTable($component);
-            $this->_tables[$component]->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, false);
-        }
-        
-        if ( ! ($subclasses = $this->_tables[$component]->getOption('subclasses'))) {
-            return $component;
-        }
-        
-        foreach ($subclasses as $subclass) {
-            $table = IPF_ORM::getTable($subclass);
-            $inheritanceMap = $table->getOption('inheritanceMap');
-            list($key, $value) = each($inheritanceMap);
-            if ( ! isset($data[$key]) || $data[$key] != $value) {
-                continue;
-            } else {
-                return $table->getComponentName();
-            }
-        }
-        return $component;
-    }
-}
diff --git a/ipf/legacy_orm/orm/import/builder.php b/ipf/legacy_orm/orm/import/builder.php
deleted file mode 100644 (file)
index 9aaae70..0000000
+++ /dev/null
@@ -1,388 +0,0 @@
-<?php
-
-class IPF_ORM_Import_Builder
-{
-    private $_baseClassPrefix = 'Base';
-    private $_baseClassesDirectory = IPF_ORM::BASE_CLASSES_DIRECTORY;
-
-    private static function varExport($var)
-    {
-        $export = var_export($var, true);
-        $export = preg_replace('#\s*\(\s*#', '(', $export);
-        $export = preg_replace('#[\s,]+\)#', ')', $export);
-        $export = preg_replace('#\s+#', ' ', $export);
-        return $export;
-    }
-
-    private static function varExportList($list)
-    {
-        $export = array();
-        foreach ($list as $var) {
-            $export[] = self::varExport($var);
-        }
-        return 'array('.implode(', ', $export).')';
-    }
-
-    private function buildTableDefinition(array $definition)
-    {
-        if (isset($definition['inheritance']['type']) && ($definition['inheritance']['type'] == 'simple' || $definition['inheritance']['type'] == 'column_aggregation')) {
-            return;
-        }
-
-        $ret = array(
-            '  public static function setTableDefinition(IPF_ORM_Table $table)',
-            '  {',
-        );
-
-        if (isset($definition['inheritance']['type']) && $definition['inheritance']['type'] == 'concrete')
-            $ret[] = '    parent::setTableDefinition($table);';
-
-        if (isset($definition['tableName']) && !empty($definition['tableName']))
-            $ret[] = "    ".'$table->setTableName(\''. $definition['tableName'].'\');';
-
-        if (isset($definition['columns']) && is_array($definition['columns']) && !empty($definition['columns']))
-            $ret[] = $this->buildColumns($definition['columns']);
-
-        if (isset($definition['ordering']) && is_array($definition['ordering']) && !empty($definition['ordering']))
-            $ret[] = '    $table->setOrdering('.self::varExportList($definition['ordering']).');';
-
-        if (isset($definition['indexes']) && is_array($definition['indexes']) && !empty($definition['indexes']))
-            foreach ($definition['indexes'] as $indexName => $definitions)
-                $ret[] = "    \$table->addIndex('" . $indexName . "', " . self::varExport($definitions) . ');';
-
-        if (isset($definition['attributes']) && is_array($definition['attributes']) && !empty($definition['attributes']))
-            $ret[] = $this->buildAttributes($definition['attributes']);
-
-        if (isset($definition['options']) && is_array($definition['options']) && !empty($definition['options']))
-            $ret[] = $this->buildOptions($definition['options']);
-
-        if (isset($definition['inheritance']['subclasses']) && !empty($definition['inheritance']['subclasses'])) {
-            $ret[] = "    ".'$table->setSubClasses('. self::varExport($definition['inheritance']['subclasses']).');';
-        }
-
-        $ret[] = '  }';
-
-        return implode(PHP_EOL, $ret);
-    }
-
-    private function buildSetUp(array $definition)
-    {
-        $ret = array();
-
-        if (isset($definition['relations']) && is_array($definition['relations']) && !empty($definition['relations'])) {
-            foreach ($definition['relations'] as $name => $relation) {
-                $class = isset($relation['class']) ? $relation['class'] : $name;
-                $alias = (isset($relation['alias']) && $relation['alias'] !== $relation['class']) ? $relation['alias'] : '';
-
-                if (!isset($relation['type']))
-                    $relation['type'] = IPF_ORM_Relation::ONE;
-
-                if ($relation['type'] === IPF_ORM_Relation::ONE ||
-                    $relation['type'] === IPF_ORM_Relation::ONE_COMPOSITE) {
-                    $r = "    \$table->hasOne('$class', '$alias'";
-                } else {
-                    $r = "    \$table->hasMany('$class', '$alias'";
-                }
-
-                $a = array();
-
-                if (isset($relation['refClass'])) {
-                    $a[] = '\'refClass\' => ' . self::varExport($relation['refClass']);
-                }
-
-                if (isset($relation['deferred']) && $relation['deferred']) {
-                    $a[] = '\'default\' => ' . self::varExport($relation['deferred']);
-                }
-
-                if (isset($relation['local']) && $relation['local']) {
-                    $a[] = '\'local\' => ' . self::varExport($relation['local']);
-                }
-
-                if (isset($relation['foreign']) && $relation['foreign']) {
-                    $a[] = '\'foreign\' => ' . self::varExport($relation['foreign']);
-                }
-
-                if (isset($relation['onDelete']) && $relation['onDelete']) {
-                    $a[] = '\'onDelete\' => ' . self::varExport($relation['onDelete']);
-                }
-
-                if (isset($relation['onUpdate']) && $relation['onUpdate']) {
-                    $a[] = '\'onUpdate\' => ' . self::varExport($relation['onUpdate']);
-                }
-
-                if (isset($relation['equal']) && $relation['equal']) {
-                    $a[] = '\'equal\' => ' . self::varExport($relation['equal']);
-                }
-
-                if (isset($relation['owningSide']) && $relation['owningSide']) {
-                    $a[] = '\'owningSide\' => ' . self::varExport($relation['owningSide']);
-                }
-
-                if (isset($relation['exclude']) && $relation['exclude']) {
-                    $a[] = '\'exclude\' => ' . self::varExport($relation['exclude']);
-                }
-
-                if (!empty($a))
-                    $r .= ', array(' . implode(', ', $a) . ')';
-
-                $ret[] = $r.');';
-            }
-        }
-
-        if (isset($definition['templates']) && is_array($definition['templates']) && !empty($definition['templates'])) {
-            $this->buildTemplates($definition['templates'], $ret);
-        }
-
-        if (isset($definition['actAs']) && is_array($definition['actAs']) && !empty($definition['actAs'])) {
-            $this->buildActAs($definition['actAs'], $ret);
-        }
-
-        if (isset($definition['listeners']) && is_array($definition['listeners']) && !empty($definition['listeners'])) {
-            foreach ($definition['listeners'] as $listener) {
-                $ret[] = "    \$table->listeners['$listener'] = new $listener;";
-            }
-        }
-
-        // If the body of the function has contents and we are using inheritance
-        // then we need call the parent::setUp() before the body of the function
-        // Class table inheritance is the only one we shouldn't call parent::setUp() for
-        if (count($ret) && isset($definition['inheritance']['type']) && $definition['inheritance']['type'] != 'class_table') {
-            array_unshift($ret, '    parent::setUp($table);');
-        }
-
-        // If we have some code for the function then lets define it and return it
-        if (count($ret)) {
-            array_unshift($ret,
-                '  public static function setUp(IPF_ORM_Table $table)',
-                '  {'
-            );
-            $ret[] = '  }';
-        }
-        return $ret;
-    }
-
-    private function buildColumns(array $columns)
-    {
-        $result = array();
-        foreach ($columns as $name => $column) {
-            $columnName = isset($column['name']) ? $column['name']:$name;
-            $build = "    ".'$table->setColumn(\'' . $columnName . '\', \'' . $column['type'] . '\'';
-
-            if ($column['length']) {
-                if (is_numeric($column['length']))
-                    $build .= ', ' . $column['length'];
-                else
-                    $build .= ', array(' . $column['length'] . ')';
-            } else {
-                $build .= ', null';
-            }
-
-            $options = $column;
-
-            // Remove name, alltypes, ntype. They are not needed in options array
-            unset($options['name']);
-            unset($options['alltypes']);
-            unset($options['ntype']);
-
-            // Remove notnull => true if the column is primary
-            // Primary columns are implied to be notnull in IPF_ORM
-            if (isset($options['primary']) && $options['primary'] == true && (isset($options['notnull']) && $options['notnull'] == true)) {
-                unset($options['notnull']);
-            }
-
-            // Remove default if the value is 0 and the column is a primary key
-            // IPF_ORM defaults to 0 if it is a primary key
-            if (isset($options['primary']) && $options['primary'] == true && (isset($options['default']) && $options['default'] == 0)) {
-                unset($options['default']);
-            }
-
-            // These can be removed if they are empty. They all default to a false/0/null value anyways
-            $remove = array('fixed', 'primary', 'notnull', 'autoincrement', 'unsigned');
-            foreach ($remove as $key) {
-                if (isset($options[$key]) && empty($options[$key])) {
-                    unset($options[$key]);
-                }
-            }
-
-            // Remove null and empty array values
-            foreach ($options as $key => $value) {
-                if (is_null($value) || (is_array($value) && empty($value))) {
-                    unset($options[$key]);
-                }
-            }
-
-            if (is_array($options) && !empty($options)) {
-                $build .= ', ' . self::varExport($options);
-            }
-
-            $build .= ');';
-
-            $result[] = $build;
-        }
-
-        return implode(PHP_EOL, $result);
-    }
-
-    private function buildTemplates(array $templates, array &$build)
-    {
-        foreach ($templates as $name => $options) {
-            if (is_array($options) && !empty($options)) {
-                $build[] = "    \$table->addTemplate('$name', " . self::varExport($options) . ");";
-            } elseif (isset($templates[0])) {
-                $build[] = "    \$table->addTemplate('$options');";
-            } else {
-                $build[] = "    \$table->addTemplate('$name');";
-            }
-        }
-    }
-
-    private function buildActAs($actAs, array &$build)
-    {
-        // rewrite special case of actAs: [Behavior] which gave [0] => Behavior
-        if (is_array($actAs) && isset($actAs[0]) && !is_array($actAs[0])) {
-            $actAs = array_flip($actAs);
-        }
-
-        // rewrite special case of actAs: Behavior
-        if (!is_array($actAs))
-            $actAs = array($actAs => '');
-
-        foreach ($actAs as $template => $options) {
-            // find class matching $name
-            if (class_exists('IPF_ORM_Template_'.$template, true))
-                $classname = 'IPF_ORM_Template_'.$template;
-            else
-                $classname = $template;
-
-            if (is_array($options))
-                $options = self::varExport($options);
-            else
-                $options = '';
-
-            $build[] = "    \$table->addTemplate(new $classname($options));";
-        }
-    }
-
-    private function buildAttributes(array $attributes)
-    {
-        $build = PHP_EOL;
-        foreach ($attributes as $key => $value) {
-
-            if (is_bool($value)) {
-                $values = $value ? 'true':'false';
-            } else {
-                if (!is_array($value))
-                    $value = array($value);
-
-                $values = '';
-                foreach ($value as $attr) {
-                    $values .= "IPF_ORM::" . strtoupper($key) . "_" . strtoupper($attr) . ' ^ ';
-                }
-
-                // Trim last ^
-                $values = substr($values, 0, strlen($values) - 3);
-            }
-
-            $build .= "    \$table->setAttribute(IPF_ORM::ATTR_" . strtoupper($key) . ", " . $values . ");" . PHP_EOL;
-        }
-
-        return $build;
-    }
-
-    private function buildOptions(array $options)
-    {
-        $build = '';
-        foreach ($options as $name => $value) {
-            $build .= "    \$table->setOption('$name', " . self::varExport($value) . ");" . PHP_EOL;
-        }
-        return $build;
-    }
-
-    public function buildRecord(array $definition, $targetPath)
-    {
-        if (!isset($definition['className']))
-            throw new IPF_ORM_Exception('Missing class name.');
-
-        $this->writeBaseDefinition($definition, $targetPath);
-        $this->writeModelDefinition($definition, $targetPath);
-    }
-
-    private function writeBaseDefinition(array $definition, $targetPath)
-    {
-        $code = array(
-            '<?php',
-            '',
-            '/**',
-            ' * This class has been auto-generated by the IPF_ORM Framework.',
-            ' * Changes to this file may cause incorrect behavior',
-            ' * and will be lost if the code is regenerated.',
-            ' */',
-            '',
-        );
-
-        $code[] = 'abstract class '.$this->_baseClassPrefix.$definition['className'].' extends IPF_ORM_Record';
-        $code[] = '{';
-        $code[] = $this->buildTableDefinition($definition);
-        $code[] = '';
-        $code   = array_merge($code, $this->buildSetUp($definition));
-        $code[] = '';
-        $code   = array_merge($code, $this->buildShortcuts($definition));
-        $code[] = '}';
-
-        $fileName = $this->_baseClassPrefix . $definition['className'] . '.php';
-        $writePath = $targetPath . DIRECTORY_SEPARATOR . $this->_baseClassesDirectory;
-        IPF_Utils::makeDirectories($writePath);
-        $writePath .= DIRECTORY_SEPARATOR . $fileName;
-
-        if (file_put_contents($writePath, implode(PHP_EOL, $code)) === false)
-            throw new IPF_ORM_Exception("Couldn't write file " . $writePath);
-    }
-
-    private function buildShortcuts(array $definition)
-    {
-        return array(
-            '  public static function table()',
-            '  {',
-            '    return IPF_ORM::getTable(\''.$definition['className'].'\');',
-            '  }',
-            '',
-            '  public static function query($alias=\'\')',
-            '  {',
-            '    return IPF_ORM::getTable(\''.$definition['className'].'\')->createQuery($alias);',
-            '  }',
-        );
-    }
-
-    private function writeModelDefinition(array $definition, $targetPath)
-    {
-        $className = $definition['className'];
-        $adminClassName = $className.'Admin';
-
-        $writePath = $targetPath . DIRECTORY_SEPARATOR . $className . '.php';
-        if (file_exists($writePath))
-            return;
-
-        $code = array(
-            '<?php',
-            '',
-            sprintf('class %s extends %s%s', $className, $this->_baseClassPrefix, $className),
-            '{',
-            '}',
-            '',
-            '/*',
-            'class '.$adminClassName.' extends IPF_Admin_Model',
-            '{',
-            '}',
-            '',
-            '// Add following line to your admin.php to enable admin interface for this model',
-            "new $adminClassName('$className'),",
-            '*/',
-            '',
-        );
-
-        IPF_Utils::makeDirectories($targetPath);
-        if (file_put_contents($writePath, implode(PHP_EOL, $code)) === false)
-            throw new IPF_ORM_Exception("Couldn't write file " . $writePath);
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/import/schema.php b/ipf/legacy_orm/orm/import/schema.php
deleted file mode 100644 (file)
index 889e070..0000000
+++ /dev/null
@@ -1,490 +0,0 @@
-<?php
-
-class IPF_ORM_Import_Schema
-{
-    protected $_relations = array();
-
-    protected $_validation = array(
-        'root' => array(
-            'abstract',
-            'connection',
-            'className',
-            'tableName',
-            'connection',
-            'relations',
-            'columns',
-            'indexes',
-            'attributes',
-            'templates',
-            'actAs',
-            'options',
-            'inheritance',
-            'detect_relations',
-            'listeners',
-            'ordering',
-        ),
-        'column' => array(
-            'name',
-            'format',
-            'fixed',
-            'primary',
-            'autoincrement',
-            'type',
-            'length',
-            'size',
-            'default',
-            'scale',
-            'values',
-            'comment',
-            'protected',
-            'zerofill',
-            'owner',
-            'exclude',
-            'collate',
-            'charset',
-        ),
-        'relation' => array(
-            'key',
-            'class',
-            'alias',
-            'type',
-            'refClass',
-            'local',
-            'exclude',
-            'foreign',
-            'foreignClass',
-            'foreignAlias',
-            'foreignType',
-            'foreignExclude',
-            'autoComplete',
-            'onDelete',
-            'onUpdate',
-            'equal',
-            'owningSide',
-        ),
-        'inheritance' => array(
-            'type',
-            'extends',
-            'keyField',
-            'keyValue',
-        ),
-    );
-
-    protected $_validators = array();
-
-    public function getValidators()
-    {
-        if (empty($this->_validators)) {
-            $this->_validators = IPF_ORM_Utils::getValidators();
-        }
-        return $this->_validators;
-    }
-
-    public function importSchema($filename, $extraAllwedReferences)
-    {
-        $filename = (array)$filename;
-        $array = array();
-        foreach ($filename as $fn) {
-            $definitions = $this->parseSchema($fn);
-            foreach ($definitions as &$definition)
-                $definition['input_file'] = $fn;
-            $array = array_merge($array, $definitions);
-        }
-
-        $array = $this->_buildRelationships($array, $extraAllwedReferences);
-        $array = $this->_processInheritance($array);
-        return $array;
-    }
-
-    private function parseSchema($schema)
-    {
-        $defaults = array('abstract'            =>  false,
-                          'className'           =>  null,
-                          'tableName'           =>  null,
-                          'connection'          =>  null,
-                          'relations'           =>  array(),
-                          'indexes'             =>  array(),
-                          'attributes'          =>  array(),
-                          'templates'           =>  array(),
-                          'actAs'               =>  array(),
-                          'options'             =>  array(),
-                          'inheritance'         =>  array(),
-                          'detect_relations'    =>  false);
-        
-        $array = Spyc::YAMLLoad($schema);
-
-        // Go through the schema and look for global values so we can assign them to each table/class
-        $globals = array();
-        $globalKeys = array('connection',
-                            'attributes',
-                            'templates',
-                            'actAs',
-                            'options',
-                            'inheritance',
-                            'detect_relations');
-
-        // Loop over and build up all the global values and remove them from the array
-        foreach ($array as $key => $value) {
-            if (in_array($key, $globalKeys)) {
-                unset($array[$key]);
-                $globals[$key] = $value;
-            }
-        }
-
-        // Apply the globals to each table if it does not have a custom value set already
-        foreach ($array as $className => $table) {
-            foreach ($globals as $key => $value) {
-                if (!isset($array[$className][$key])) {
-                    $array[$className][$key] = $value;
-                }
-            }
-        }
-
-        $build = array();
-
-        foreach ($array as $className => $table) {
-            $this->_validateSchemaElement('root', array_keys($table), $className);
-
-            $columns = array();
-
-            $className = isset($table['className']) ? (string) $table['className']:(string) $className;
-
-            if (isset($table['inheritance']['keyField']) || isset($table['inheritance']['keyValue'])) {
-                $table['inheritance']['type'] = 'column_aggregation';
-            }
-
-            if (isset($table['tableName']) && $table['tableName']) {
-                $tableName = $table['tableName'];
-            } else {
-                if (isset($table['inheritance']['type']) && ($table['inheritance']['type'] == 'column_aggregation')) {
-                    $tableName = null;
-                } else {
-                    $tableName = IPF_ORM_Inflector::tableize($className);
-                }
-            }
-
-            $connection = isset($table['connection']) ? $table['connection']:'current';
-
-            $columns = isset($table['columns']) ? $table['columns']:array();
-
-            if ( ! empty($columns)) {
-                foreach ($columns as $columnName => $field) {
-
-                    // Support short syntax: my_column: integer(4)
-                    if ( ! is_array($field)) {
-                        $original = $field;
-                        $field = array();
-                        $field['type'] = $original;
-                    }
-
-                    $colDesc = array();
-                    if (isset($field['name'])) {
-                        $colDesc['name'] = $field['name'];
-                    } else {
-                        $colDesc['name'] = $columnName;
-                    }
-
-                    $this->_validateSchemaElement('column', array_keys($field), $className . '->columns->' . $colDesc['name']);
-
-                    // Support short type(length) syntax: my_column: { type: integer(4) }
-                    $e = explode('(', $field['type']);
-                    if (isset($e[0]) && isset($e[1])) {
-                        $colDesc['type'] = $e[0];
-                        $colDesc['length'] = substr($e[1], 0, strlen($e[1]) - 1);
-                    } else {
-                        $colDesc['type'] = isset($field['type']) ? (string) $field['type']:null;
-                        $colDesc['length'] = isset($field['length']) ? (int) $field['length']:null;
-                        $colDesc['length'] = isset($field['size']) ? (int) $field['size']:$colDesc['length'];
-                    }
-
-                    $colDesc['fixed'] = isset($field['fixed']) ? (int) $field['fixed']:null;
-                    $colDesc['primary'] = isset($field['primary']) ? (bool) (isset($field['primary']) && $field['primary']):null;
-                    $colDesc['default'] = isset($field['default']) ? $field['default']:null;
-                    $colDesc['autoincrement'] = isset($field['autoincrement']) ? (bool) (isset($field['autoincrement']) && $field['autoincrement']):null;
-                    $colDesc['values'] = isset($field['values']) ? (array) $field['values']:null;
-
-                    // Include all the specified and valid validators in the colDesc
-                    $validators = $this->getValidators();
-
-                    foreach ($validators as $validator) {
-                        if (isset($field[$validator])) {
-                            $colDesc[$validator] = $field[$validator];
-                        }
-                    }
-
-                    $columns[(string) $columnName] = $colDesc;
-                }
-            }
-
-            // Apply the default values
-            foreach ($defaults as $key => $defaultValue) {
-                if (isset($table[$key]) && ! isset($build[$className][$key])) {
-                    $build[$className][$key] = $table[$key];
-                } else {
-                    $build[$className][$key] = isset($build[$className][$key]) ? $build[$className][$key]:$defaultValue;
-                }
-            }
-            
-            $build[$className]['className'] = $className;
-            $build[$className]['tableName'] = $tableName;
-            $build[$className]['columns'] = $columns;
-            
-            // Make sure that anything else that is specified in the schema makes it to the final array
-            $build[$className] = IPF_ORM_Utils::arrayDeepMerge($table, $build[$className]);
-            
-            // We need to keep track of the className for the connection
-            $build[$className]['connectionClassName'] = $build[$className]['className'];
-        }
-
-        return $build;
-    }
-
-    protected function _processInheritance($array)
-    {
-        // Apply default inheritance configuration
-        foreach ($array as $className => $definition) {
-            if ( ! empty($array[$className]['inheritance'])) {
-                $this->_validateSchemaElement('inheritance', array_keys($definition['inheritance']), $className . '->inheritance');
-
-                // Default inheritance to concrete inheritance
-                if ( ! isset($array[$className]['inheritance']['type'])) {
-                    $array[$className]['inheritance']['type'] = 'concrete';
-                }
-
-                // Some magic for setting up the keyField and keyValue column aggregation options
-                // Adds keyField to the parent class automatically
-                if ($array[$className]['inheritance']['type'] == 'column_aggregation') {
-                    // Set the keyField to 'type' by default
-                    if ( ! isset($array[$className]['inheritance']['keyField'])) {
-                        $array[$className]['inheritance']['keyField'] = 'type';                        
-                    }
-                    
-                    // Set the keyValue to the name of the child class if it does not exist
-                    if ( ! isset($array[$className]['inheritance']['keyValue'])) {
-                        $array[$className]['inheritance']['keyValue'] = $className;
-                    }
-                    
-                    // Add the keyType column to the parent if a definition does not already exist
-                    if ( ! isset($array[$array[$className]['inheritance']['extends']]['columns'][$array[$className]['inheritance']['keyField']])) {
-                        $array[$definition['inheritance']['extends']]['columns'][$array[$className]['inheritance']['keyField']] = array('name' => $array[$className]['inheritance']['keyField'], 'type' => 'string', 'length' => 255);
-                    }
-                }
-            }
-        }
-
-        // Array of the array keys to move to the parent, and the value to default the child definition to
-        // after moving it. Will also populate the subclasses array for the inheritance parent
-        $moves = array('columns' => array());
-        
-        foreach ($array as $className => $definition) {
-            if (!isset($definition['className']))
-                continue;
-            $parent = $this->_findBaseSuperClass($array, $definition['className']);
-            // Move any definitions on the schema to the parent
-            if (isset($definition['inheritance']['extends']) && isset($definition['inheritance']['type']) && ($definition['inheritance']['type'] == 'simple' || $definition['inheritance']['type'] == 'column_aggregation')) {
-                foreach ($moves as $move => $resetValue) {
-                    $array[$parent][$move] = IPF_ORM_Utils::arrayDeepMerge($array[$parent][$move], $definition[$move]);
-                    $array[$definition['className']][$move] = $resetValue;
-                }
-
-                // Populate the parents subclasses
-                if ($definition['inheritance']['type'] == 'column_aggregation') {
-                    $array[$parent]['inheritance']['subclasses'][$definition['className']] = array($definition['inheritance']['keyField'] => $definition['inheritance']['keyValue']);
-                }
-            }
-        }
-
-        return $array;
-    }
-
-    protected function _findBaseSuperClass($array, $class)
-    {
-        if (isset($array[$class]['inheritance']['extends'])) {
-            return $this->_findBaseSuperClass($array, $array[$class]['inheritance']['extends']);
-        } else {
-            return $class;
-        }
-    }
-
-    protected function _buildRelationships($array, $extraAllwedReferences)
-    {
-        // Handle auto detecting relations by the names of columns
-        // User.contact_id will automatically create User hasOne Contact local => contact_id, foreign => id
-        foreach ($array as $className => $properties) {
-            if (isset($properties['columns']) && ! empty($properties['columns']) && isset($properties['detect_relations']) && $properties['detect_relations']) {
-                foreach ($properties['columns'] as $column) {
-                    // Check if the column we are inflecting has a _id on the end of it before trying to inflect it and find
-                    // the class name for the column
-                    if (strpos($column['name'], '_id')) {
-                        $columnClassName = IPF_ORM_Inflector::classify(str_replace('_id', '', $column['name']));
-                        if (isset($array[$columnClassName]) && !isset($array[$className]['relations'][$columnClassName])) {
-                            $array[$className]['relations'][$columnClassName] = array();
-
-                            // Set the detected foreign key type and length to the same as the primary key
-                            // of the related table
-                            $type = isset($array[$columnClassName]['columns']['id']['type']) ? $array[$columnClassName]['columns']['id']['type']:'integer';
-                            $length = isset($array[$columnClassName]['columns']['id']['length']) ? $array[$columnClassName]['columns']['id']['length']:8;
-                            $array[$className]['columns'][$column['name']]['type'] = $type;
-                            $array[$className]['columns'][$column['name']]['length'] = $length;
-                        }
-                    }
-                }
-            }
-        }
-
-        foreach ($array as $name => $properties) {
-            if (!isset($properties['relations'])) {
-                continue;
-            }
-
-            $className = $properties['className'];
-            $relations = $properties['relations'];
-
-            foreach ($relations as $alias => $relation) {
-                $class = isset($relation['class']) ? $relation['class'] : $alias;
-                if (!isset($array[$class]) && !in_array($class, $extraAllwedReferences)) {
-                    print "Warning: Ignoring relation to unknown model '$class' in model '$className'. \n";
-                    continue;
-                }
-                $relation['class'] = $class;
-                $relation['alias'] = isset($relation['alias']) ? $relation['alias'] : $alias;
-                
-                // Attempt to guess the local and foreign
-                if (isset($relation['refClass'])) {
-                    $relation['local'] = isset($relation['local']) ? $relation['local']:IPF_ORM_Inflector::tableize($name) . '_id';
-                    $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:IPF_ORM_Inflector::tableize($class) . '_id';
-                } else {
-                    $relation['local'] = isset($relation['local']) ? $relation['local']:IPF_ORM_Inflector::tableize($relation['class']) . '_id';
-                    $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:'id';
-                }
-                
-                if (isset($relation['refClass'])) {
-                    $relation['type'] = 'many';
-                }
-                
-                if (isset($relation['type']) && $relation['type']) {
-                    $relation['type'] = $relation['type'] === 'one' ? IPF_ORM_Relation::ONE:IPF_ORM_Relation::MANY;
-                } else {
-                    $relation['type'] = IPF_ORM_Relation::ONE;
-                }
-
-                if (isset($relation['foreignType']) && $relation['foreignType']) {
-                    $relation['foreignType'] = $relation['foreignType'] === 'one' ? IPF_ORM_Relation::ONE:IPF_ORM_Relation::MANY;
-                }
-                
-                $relation['key'] = $this->_buildUniqueRelationKey($relation);
-
-                $this->_validateSchemaElement('relation', array_keys($relation), $className . '->relation->' . $relation['alias']);
-                
-                $this->_relations[$className][$alias] = $relation;
-            }
-        }
-
-        // Now we auto-complete opposite ends of relationships
-        $this->_autoCompleteOppositeRelations($extraAllwedReferences);
-
-        // Make sure we do not have any duplicate relations
-        $this->_fixDuplicateRelations();
-
-        //$array['relations'];
-        // Set the full array of relationships for each class to the final array
-        foreach ($this->_relations as $className => $relations) {
-            if (!in_array($className, $extraAllwedReferences))
-                $array[$className]['relations'] = $relations;
-        }
-
-        return $array;
-    }
-
-    protected function _autoCompleteOppositeRelations($extraAllwedReferences)
-    {
-        foreach ($this->_relations as $className => $relations) {
-            if (in_array($className, $extraAllwedReferences))
-                continue;
-
-            foreach ($relations as $alias => $relation) {
-                if ((isset($relation['equal']) && $relation['equal']) || (isset($relation['autoComplete']) && $relation['autoComplete'] === false)) {
-                    continue;
-                }
-                
-                $newRelation = array();
-                $newRelation['foreign'] = $relation['local'];
-                $newRelation['local'] = $relation['foreign'];
-                $newRelation['class'] = isset($relation['foreignClass']) ? $relation['foreignClass'] : $className;
-                $newRelation['alias'] = isset($relation['foreignAlias']) ? $relation['foreignAlias'] : $className;
-                $newRelation['exclude'] = isset($relation['foreignExclude']) ? $relation['foreignExclude'] : false;
-
-                // this is so that we know that this relation was autogenerated and
-                // that we do not need to include it if it is explicitly declared in the schema by the users.
-                $newRelation['autogenerated'] = true; 
-
-                if (isset($relation['refClass'])) {
-                    $newRelation['refClass'] = $relation['refClass'];
-                    $newRelation['type'] = isset($relation['foreignType']) ? $relation['foreignType']:$relation['type'];
-                } else {                
-                    if(isset($relation['foreignType'])) {
-                        $newRelation['type'] = $relation['foreignType'];
-                    } else {
-                        $newRelation['type'] = $relation['type'] === IPF_ORM_Relation::ONE ? IPF_ORM_Relation::MANY:IPF_ORM_Relation::ONE;
-                    }
-                }
-
-                // Make sure it doesn't already exist
-                if ( ! isset($this->_relations[$relation['class']][$newRelation['alias']])) {
-                    $newRelation['key'] = $this->_buildUniqueRelationKey($newRelation);
-                    $this->_relations[$relation['class']][$newRelation['alias']] = $newRelation;
-                }
-            }
-        }
-    }
-
-    protected function _fixDuplicateRelations()
-    {
-        foreach($this->_relations as $className => $relations) {
-            // This is for checking for duplicates between alias-relations and a auto-generated relations to ensure the result set of unique relations
-            $existingRelations = array();
-            $uniqueRelations = array();
-            foreach ($relations as $relation) {
-                if ( ! in_array($relation['key'], $existingRelations)) {
-                    $existingRelations[] = $relation['key'];
-                    $uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation));
-                } else {
-                    // check to see if this relationship is not autogenerated, if it's not, then the user must have explicitly declared it
-                    if ( ! isset($relation['autogenerated']) || $relation['autogenerated'] != true) {
-                        $uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation));
-                    }
-                }
-            }
-            
-            $this->_relations[$className] = $uniqueRelations;
-        }
-    }
-
-    protected function _buildUniqueRelationKey($relation)
-    {
-        return md5($relation['local'].$relation['foreign'].$relation['class'].(isset($relation['refClass']) ? $relation['refClass']:null));
-    }
-
-    protected function _validateSchemaElement($name, $element, $path)
-    {
-        $element = (array) $element;
-
-        $validation = $this->_validation[$name];
-
-        // Validators are a part of the column validation
-        // This should be fixed, made cleaner
-        if ($name == 'column') {
-            $validators = $this->getValidators();
-            $validation = array_merge($validation, $validators);
-        }
-
-        $validation = array_flip($validation);
-        foreach ($element as $key => $value) {
-            if ( ! isset($validation[$value])) {
-                throw new IPF_ORM_Exception(sprintf('Invalid schema element named "' . $value .
-                    '" at path "' . $path . '"'));
-            }
-        }
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/inflector.php b/ipf/legacy_orm/orm/inflector.php
deleted file mode 100644 (file)
index 4946ac5..0000000
+++ /dev/null
@@ -1,341 +0,0 @@
-<?php
-
-class IPF_ORM_Inflector
-{
-    public static function pluralize($word)
-    {
-        $plural = array('/(quiz)$/i' => '\1zes',
-                        '/^(ox)$/i' => '\1en',
-                        '/([m|l])ouse$/i' => '\1ice',
-                        '/(matr|vert|ind)ix|ex$/i' => '\1ices',
-                        '/(x|ch|ss|sh)$/i' => '\1es',
-                        '/([^aeiouy]|qu)ies$/i' => '\1y',
-                        '/([^aeiouy]|qu)y$/i' => '\1ies',
-                        '/(hive)$/i' => '\1s',
-                        '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
-                        '/sis$/i' => 'ses',
-                        '/([ti])um$/i' => '\1a',
-                        '/(buffal|tomat)o$/i' => '\1oes',
-                        '/(bu)s$/i' => '\1ses',
-                        '/(alias|status)/i' => '\1es',
-                        '/(octop|vir)us$/i' => '\1i',
-                        '/(ax|test)is$/i' => '\1es',
-                        '/s$/i' => 's',
-                        '/$/' => 's');
-
-        $uncountable = array('equipment',
-                             'information',
-                             'rice',
-                             'money',
-                             'species',
-                             'series',
-                             'fish',
-                             'sheep');
-
-        $irregular = array('person' => 'people',
-                           'man'    => 'men',
-                           'child'  => 'children',
-                           'sex'    => 'sexes',
-                           'move'   => 'moves');
-
-        $lowercasedWord = strtolower($word);
-
-        foreach ($uncountable as $_uncountable) {
-            if (substr($lowercasedWord, (-1 * strlen($_uncountable))) == $_uncountable) {
-                return $word;
-            }
-        }
-
-        foreach ($irregular as $_plural=> $_singular) {
-            if (preg_match('/('.$_plural.')$/i', $word, $arr)) {
-                return preg_replace('/('.$_plural.')$/i', substr($arr[0],0,1) . substr($_singular,1), $word);
-            }
-        }
-
-        foreach ($plural as $rule => $replacement) {
-            if (preg_match($rule, $word)) {
-                return preg_replace($rule, $replacement, $word);
-            }
-        }
-
-        return false;
-    }
-
-    public static function singularize($word)
-    {
-        $singular = array('/(quiz)zes$/i' => '\\1',
-                          '/(matr)ices$/i' => '\\1ix',
-                          '/(vert|ind)ices$/i' => '\\1ex',
-                          '/^(ox)en/i' => '\\1',
-                          '/(alias|status)es$/i' => '\\1',
-                          '/([octop|vir])i$/i' => '\\1us',
-                          '/(cris|ax|test)es$/i' => '\\1is',
-                          '/(shoe)s$/i' => '\\1',
-                          '/(o)es$/i' => '\\1',
-                          '/(bus)es$/i' => '\\1',
-                          '/([m|l])ice$/i' => '\\1ouse',
-                          '/(x|ch|ss|sh)es$/i' => '\\1',
-                          '/(m)ovies$/i' => '\\1ovie',
-                          '/(s)eries$/i' => '\\1eries',
-                          '/([^aeiouy]|qu)ies$/i' => '\\1y',
-                          '/([lr])ves$/i' => '\\1f',
-                          '/(tive)s$/i' => '\\1',
-                          '/(hive)s$/i' => '\\1',
-                          '/([^f])ves$/i' => '\\1fe',
-                          '/(^analy)ses$/i' => '\\1sis',
-                          '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\\1\\2sis',
-                          '/([ti])a$/i' => '\\1um',
-                          '/(n)ews$/i' => '\\1ews',
-                          '/^(.{2,2})$/i' => '\\1',
-                          '/s$/i' => '');
-
-        $uncountable = array('equipment',
-                             'information',
-                             'rice',
-                             'money',
-                             'species',
-                             'series',
-                             'fish',
-                             'sheep',
-                             'sms',
-                             'status',
-                             'access');
-
-        $irregular = array('person' => 'people',
-                           'man'    => 'men',
-                           'child'  => 'children',
-                           'sex'    => 'sexes',
-                           'move'   => 'moves');
-
-        $lowercasedWord = strtolower($word);
-        foreach ($uncountable as $_uncountable) {
-            if (substr($lowercasedWord, ( -1 * strlen($_uncountable))) == $_uncountable) {
-                return $word;
-            }
-        }
-
-        foreach ($irregular as $_singular => $_plural) {
-            if (preg_match('/('.$_plural.')$/i', $word, $arr)) {
-                return preg_replace('/('.$_plural.')$/i', substr($arr[0],0,1).substr($_singular,1), $word);
-            }
-        }
-
-        foreach ($singular as $rule => $replacement) {
-            if (preg_match($rule, $word)) {
-                return preg_replace($rule, $replacement, $word);
-            }
-        }
-
-        return $word;
-    }
-
-    public static function tableize($word)
-    {
-        // Would prefer this but it breaks unit tests. Forces the table underscore pattern
-        // return self::pluralize(self::underscore($name));
-        return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word));
-    }
-
-    public static function classify($word)
-    {
-        return preg_replace_callback('~(_?)(_)([\w])~', array("IPF_ORM_Inflector", "classifyCallback"), ucfirst(strtolower($word)));
-    }
-
-    public static function classifyCallback($matches)
-    {
-        return $matches[1] . strtoupper($matches[3]);
-    }
-
-    public static function seemsUtf8($string)
-    {
-        for ($i = 0; $i < strlen($string); $i++) {
-            if (ord($string[$i]) < 0x80) continue; # 0bbbbbbb
-            elseif ((ord($string[$i]) & 0xE0) == 0xC0) $n=1; # 110bbbbb
-            elseif ((ord($string[$i]) & 0xF0) == 0xE0) $n=2; # 1110bbbb
-            elseif ((ord($string[$i]) & 0xF8) == 0xF0) $n=3; # 11110bbb
-            elseif ((ord($string[$i]) & 0xFC) == 0xF8) $n=4; # 111110bb
-            elseif ((ord($string[$i]) & 0xFE) == 0xFC) $n=5; # 1111110b
-            else return false; # Does not match any model
-            for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ?
-                if ((++$i == strlen($string)) || ((ord($string[$i]) & 0xC0) != 0x80))
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public static function unaccent($string)
-    {
-        if (!preg_match('/[\x80-\xff]/', $string)) {
-            return $string;
-        }
-
-        if (self::seemsUtf8($string)) {
-            $chars = array(
-            // Decompositions for Latin-1 Supplement
-            chr(195).chr(128) => 'A', chr(195).chr(129) => 'A',
-            chr(195).chr(130) => 'A', chr(195).chr(131) => 'A',
-            chr(195).chr(132) => 'A', chr(195).chr(133) => 'A',
-            chr(195).chr(135) => 'C', chr(195).chr(136) => 'E',
-            chr(195).chr(137) => 'E', chr(195).chr(138) => 'E',
-            chr(195).chr(139) => 'E', chr(195).chr(140) => 'I',
-            chr(195).chr(141) => 'I', chr(195).chr(142) => 'I',
-            chr(195).chr(143) => 'I', chr(195).chr(145) => 'N',
-            chr(195).chr(146) => 'O', chr(195).chr(147) => 'O',
-            chr(195).chr(148) => 'O', chr(195).chr(149) => 'O',
-            chr(195).chr(150) => 'O', chr(195).chr(153) => 'U',
-            chr(195).chr(154) => 'U', chr(195).chr(155) => 'U',
-            chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y',
-            chr(195).chr(159) => 's', chr(195).chr(160) => 'a',
-            chr(195).chr(161) => 'a', chr(195).chr(162) => 'a',
-            chr(195).chr(163) => 'a', chr(195).chr(164) => 'a',
-            chr(195).chr(165) => 'a', chr(195).chr(167) => 'c',
-            chr(195).chr(168) => 'e', chr(195).chr(169) => 'e',
-            chr(195).chr(170) => 'e', chr(195).chr(171) => 'e',
-            chr(195).chr(172) => 'i', chr(195).chr(173) => 'i',
-            chr(195).chr(174) => 'i', chr(195).chr(175) => 'i',
-            chr(195).chr(177) => 'n', chr(195).chr(178) => 'o',
-            chr(195).chr(179) => 'o', chr(195).chr(180) => 'o',
-            chr(195).chr(181) => 'o', chr(195).chr(182) => 'o',
-            chr(195).chr(182) => 'o', chr(195).chr(185) => 'u',
-            chr(195).chr(186) => 'u', chr(195).chr(187) => 'u',
-            chr(195).chr(188) => 'u', chr(195).chr(189) => 'y',
-            chr(195).chr(191) => 'y',
-            // Decompositions for Latin Extended-A
-            chr(196).chr(128) => 'A', chr(196).chr(129) => 'a',
-            chr(196).chr(130) => 'A', chr(196).chr(131) => 'a',
-            chr(196).chr(132) => 'A', chr(196).chr(133) => 'a',
-            chr(196).chr(134) => 'C', chr(196).chr(135) => 'c',
-            chr(196).chr(136) => 'C', chr(196).chr(137) => 'c',
-            chr(196).chr(138) => 'C', chr(196).chr(139) => 'c',
-            chr(196).chr(140) => 'C', chr(196).chr(141) => 'c',
-            chr(196).chr(142) => 'D', chr(196).chr(143) => 'd',
-            chr(196).chr(144) => 'D', chr(196).chr(145) => 'd',
-            chr(196).chr(146) => 'E', chr(196).chr(147) => 'e',
-            chr(196).chr(148) => 'E', chr(196).chr(149) => 'e',
-            chr(196).chr(150) => 'E', chr(196).chr(151) => 'e',
-            chr(196).chr(152) => 'E', chr(196).chr(153) => 'e',
-            chr(196).chr(154) => 'E', chr(196).chr(155) => 'e',
-            chr(196).chr(156) => 'G', chr(196).chr(157) => 'g',
-            chr(196).chr(158) => 'G', chr(196).chr(159) => 'g',
-            chr(196).chr(160) => 'G', chr(196).chr(161) => 'g',
-            chr(196).chr(162) => 'G', chr(196).chr(163) => 'g',
-            chr(196).chr(164) => 'H', chr(196).chr(165) => 'h',
-            chr(196).chr(166) => 'H', chr(196).chr(167) => 'h',
-            chr(196).chr(168) => 'I', chr(196).chr(169) => 'i',
-            chr(196).chr(170) => 'I', chr(196).chr(171) => 'i',
-            chr(196).chr(172) => 'I', chr(196).chr(173) => 'i',
-            chr(196).chr(174) => 'I', chr(196).chr(175) => 'i',
-            chr(196).chr(176) => 'I', chr(196).chr(177) => 'i',
-            chr(196).chr(178) => 'IJ',chr(196).chr(179) => 'ij',
-            chr(196).chr(180) => 'J', chr(196).chr(181) => 'j',
-            chr(196).chr(182) => 'K', chr(196).chr(183) => 'k',
-            chr(196).chr(184) => 'k', chr(196).chr(185) => 'L',
-            chr(196).chr(186) => 'l', chr(196).chr(187) => 'L',
-            chr(196).chr(188) => 'l', chr(196).chr(189) => 'L',
-            chr(196).chr(190) => 'l', chr(196).chr(191) => 'L',
-            chr(197).chr(128) => 'l', chr(197).chr(129) => 'L',
-            chr(197).chr(130) => 'l', chr(197).chr(131) => 'N',
-            chr(197).chr(132) => 'n', chr(197).chr(133) => 'N',
-            chr(197).chr(134) => 'n', chr(197).chr(135) => 'N',
-            chr(197).chr(136) => 'n', chr(197).chr(137) => 'N',
-            chr(197).chr(138) => 'n', chr(197).chr(139) => 'N',
-            chr(197).chr(140) => 'O', chr(197).chr(141) => 'o',
-            chr(197).chr(142) => 'O', chr(197).chr(143) => 'o',
-            chr(197).chr(144) => 'O', chr(197).chr(145) => 'o',
-            chr(197).chr(146) => 'OE',chr(197).chr(147) => 'oe',
-            chr(197).chr(148) => 'R', chr(197).chr(149) => 'r',
-            chr(197).chr(150) => 'R', chr(197).chr(151) => 'r',
-            chr(197).chr(152) => 'R', chr(197).chr(153) => 'r',
-            chr(197).chr(154) => 'S', chr(197).chr(155) => 's',
-            chr(197).chr(156) => 'S', chr(197).chr(157) => 's',
-            chr(197).chr(158) => 'S', chr(197).chr(159) => 's',
-            chr(197).chr(160) => 'S', chr(197).chr(161) => 's',
-            chr(197).chr(162) => 'T', chr(197).chr(163) => 't',
-            chr(197).chr(164) => 'T', chr(197).chr(165) => 't',
-            chr(197).chr(166) => 'T', chr(197).chr(167) => 't',
-            chr(197).chr(168) => 'U', chr(197).chr(169) => 'u',
-            chr(197).chr(170) => 'U', chr(197).chr(171) => 'u',
-            chr(197).chr(172) => 'U', chr(197).chr(173) => 'u',
-            chr(197).chr(174) => 'U', chr(197).chr(175) => 'u',
-            chr(197).chr(176) => 'U', chr(197).chr(177) => 'u',
-            chr(197).chr(178) => 'U', chr(197).chr(179) => 'u',
-            chr(197).chr(180) => 'W', chr(197).chr(181) => 'w',
-            chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y',
-            chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z',
-            chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z',
-            chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z',
-            chr(197).chr(190) => 'z', chr(197).chr(191) => 's',
-            // Euro Sign
-            chr(226).chr(130).chr(172) => 'E',
-            // GBP (Pound) Sign
-            chr(194).chr(163) => '');
-
-            $string = strtr($string, $chars);
-        } else {
-            // Assume ISO-8859-1 if not UTF-8
-            $chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158)
-                .chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194)
-                .chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202)
-                .chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210)
-                .chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218)
-                .chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227)
-                .chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235)
-                .chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243)
-                .chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251)
-                .chr(252).chr(253).chr(255);
-
-            $chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy";
-
-            $string = strtr($string, $chars['in'], $chars['out']);
-            $doubleChars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254));
-            $doubleChars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th');
-            $string = str_replace($doubleChars['in'], $doubleChars['out'], $string);
-        }
-
-        return $string;
-    }
-
-    public static function translit($st)
-    {
-        $st = strtr($st, array(
-            'а' => 'a',  'б' => 'b',    'в' => 'v',  'г' => 'g',  'д' => 'd',
-            'е' => 'e',  'ё' => 'e',    'ж' => 'zh', 'з' => 'z',  'и' => 'i',
-            'й' => 'y',  'к' => 'k',    'л' => 'l',  'м' => 'm',  'н' => 'n',
-            'о' => 'o',  'п' => 'p',    'р' => 'r',  'с' => 's',  'т' => 't',
-            'у' => 'u',  'ф' => 'f',    'х' => 'h',  'ц' => 'ts', 'ч' => 'ch',
-            'ш' => 'sh', 'щ' => 'shch', 'ъ' => '\'', 'ы' => 'i',  'ь' => '',
-            'э' => 'e',  'ю' => 'yu',   'я' => 'ya', 'ї' => 'i',  'є' => 'ie',
-
-            'А' => 'A',  'Б' => 'B',    'В' => 'V',  'Г' => 'G',  'Д' => 'D',
-            'Е' => 'E',  'Ё' => 'E',    'Ж' => 'Zh', 'З' => 'Z',  'И' => 'I',
-            'Й' => 'Y',  'К' => 'K',    'Л' => 'L',  'М' => 'M',  'Н' => 'N',
-            'О' => 'O',  'П' => 'P',    'Р' => 'R',  'С' => 'S',  'Т' => 'T',
-            'У' => 'U',  'Ф' => 'F',    'Х' => 'H',  'Ц' => 'Ts', 'Ч' => 'Ch',
-            'Ш' => 'Sh', 'Щ' => 'Shch', 'Ъ' => '\'', 'Ы' => 'I',  'Ь' => '',
-            'Э' => 'E',  'Ю' => 'Yu',   'Я' => 'Ya', 'Ї' => 'Yi', 'Є' => 'Ye',
-        ));
-        return $st;
-    }
-
-    public static function urlize($text)
-    {
-        // Remove all non url friendly characters with the unaccent function
-        $text = self::unaccent($text);
-        
-        // Transliterate cyrillic
-        $text = self::translit($text);
-
-        // Remove all none word characters
-        $text = preg_replace('/\W/', ' ', $text);
-        
-        // More stripping. Replace spaces with dashes
-        $text = strtolower(preg_replace('/[^A-Z^a-z^0-9^\/]+/', '-',
-                           preg_replace('/([a-z\d])([A-Z])/', '\1_\2',
-                           preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2',
-                           preg_replace('/::/', '/', $text)))));
-        
-        return trim($text, '-');
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/manager.php b/ipf/legacy_orm/orm/manager.php
deleted file mode 100644 (file)
index 570de33..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-
-class IPF_ORM_Manager extends IPF_ORM_Configurable
-{
-    protected $_connection = null;
-    protected $_queryRegistry;
-    public $dbListeners = array();
-
-    private function __construct()
-    {
-        $this->attributes = array(
-            IPF_ORM::ATTR_LOAD_REFERENCES         => true,
-            IPF_ORM::ATTR_PORTABILITY             => IPF_ORM::PORTABILITY_ALL,
-            IPF_ORM::ATTR_EXPORT                  => IPF_ORM::EXPORT_ALL,
-            IPF_ORM::ATTR_DECIMAL_PLACES          => 2,
-            IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE => 'ipf',
-        );
-    }
-
-    public static function getInstance()
-    {
-        static $instance;
-        if ( ! isset($instance)) {
-            $instance = new self();
-        }
-        return $instance;
-    }
-
-    public function getQueryRegistry()
-    {
-        if ( ! isset($this->_queryRegistry)) {
-            $this->_queryRegistry = new IPF_ORM_Query_Registry;
-        }
-        return $this->_queryRegistry;
-    }
-
-    public function setQueryRegistry(IPF_ORM_Query_Registry $registry)
-    {
-        $this->_queryRegistry = $registry;
-        
-        return $this;
-    }
-
-    public static function connection()
-    {
-        return IPF_ORM_Manager::getInstance()->getCurrentConnection();
-    }
-
-    public function getCurrentConnection()
-    {
-        if (!$this->_connection) {
-            $pdo = \PFF\Container::databaseConnection();
-            $this->_connection = new IPF_ORM_Connection_Mysql($this, $pdo);
-        }
-        return $this->_connection;
-    }
-
-    public function __toString()
-    {
-        return "<pre>\nIPF_ORM_Manager\n</pre>";
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/null.php b/ipf/legacy_orm/orm/null.php
deleted file mode 100644 (file)
index 6347618..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-final class IPF_ORM_Null
-{
-    private static $instance = null;
-    public static function getInstance()
-    {
-        if (!self::$instance)
-            self::$instance = new IPF_ORM_Null;
-        return self::$instance;
-    }
-
-    private function __construct()
-    {
-    }
-
-    private function __clone()
-    {
-    }
-
-    public static function isNull($obj)
-    {
-        return $obj === self::$instance;
-    }
-
-    public function exists()
-    {
-        return false;
-    }
-
-    public function __toString()
-    {
-        return '';
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/pager.php b/ipf/legacy_orm/orm/pager.php
deleted file mode 100644 (file)
index 744f44e..0000000
+++ /dev/null
@@ -1,265 +0,0 @@
-<?php
-
-class IPF_ORM_Pager
-{
-    protected $_query;
-    protected $_countQuery;
-    protected $_countQueryParams;
-    protected $_numResults;
-    protected $_maxPerPage;
-    protected $_page;
-    protected $_lastPage;
-    protected $_executed;
-
-    public function __construct($query, $page, $maxPerPage = 0)
-    {
-        $this->_setExecuted(false);
-
-        $this->_setQuery($query);
-        $this->_setPage($page);
-
-        $this->setMaxPerPage($maxPerPage);
-    }
-
-    protected function _initialize($params = array())
-    {
-        // retrieve the number of items found
-        $count = $this->getCountQuery()->count($this->getCountQueryParams($params));
-        
-        $this->_setNumResults($count);
-        $this->_setExecuted(true); // _adjustOffset relies of _executed equals true = getNumResults()
-
-        $this->_adjustOffset();
-    }
-
-    protected function _adjustOffset()
-    {
-        // Define new total of pages
-        $this->_setLastPage(
-            max(1, ceil($this->getNumResults() / $this->getMaxPerPage()))
-        );
-        $offset = ($this->getPage() - 1) * $this->getMaxPerPage();
-
-        // Assign new offset and limit to IPF_ORM_Query object
-        $p = $this->getQuery();
-        $p->offset($offset);
-        $p->limit($this->getMaxPerPage());
-    }
-
-    public function getExecuted()
-    {
-        return $this->_executed;
-    }
-
-    protected function _setExecuted($executed)
-    {
-        $this->_executed = $executed;
-    }
-
-    public function getRange($rangeStyle, $options = array())
-    {
-        $class = 'IPF_ORM_Pager_Range_' . ucfirst($rangeStyle);
-        return new $class($options, $this);
-    }
-
-    public function getNumResults()
-    {
-        if ($this->getExecuted()) {
-            return $this->_numResults;
-        }
-        throw new IPF_ORM_Exception(
-            'Cannot retrieve the number of results of a not yet executed Pager query'
-        );
-    }
-
-    protected function _setNumResults($nb)
-    {
-        $this->_numResults = $nb;
-    }
-
-    public function getFirstPage()
-    {
-        return 1;
-    }
-
-    public function getLastPage()
-    {
-        if ($this->getExecuted()) {
-            return $this->_lastPage;
-        }
-
-        throw new IPF_ORM_Exception(
-            'Cannot retrieve the last page number of a not yet executed Pager query'
-        );
-    }
-
-    protected function _setLastPage($page)
-    {
-        $this->_lastPage = $page;
-
-        if ($this->getPage() > $page) {
-            $this->_setPage($page);
-        }
-    }
-
-    public function getPage()
-    {
-        return $this->_page;
-    }
-
-    public function getNextPage()
-    {
-        if ($this->getExecuted()) {
-            return min($this->getPage() + 1, $this->getLastPage());
-        }
-
-        throw new IPF_ORM_Exception(
-            'Cannot retrieve the last page number of a not yet executed Pager query'
-        );
-    }
-
-    public function getPreviousPage()
-    {
-        if ($this->getExecuted()) {
-            return max($this->getPage() - 1, $this->getFirstPage());
-        }
-
-        throw new IPF_ORM_Exception(
-            'Cannot retrieve the previous page number of a not yet executed Pager query'
-        );
-    }
-
-    public function getFirstIndice()
-    {
-        return ($this->getPage() - 1) * $this->getMaxPerPage() + 1;
-    }
-
-    public function getLastIndice()
-    {
-        return min($this->getNumResults(), ($this->getPage() * $this->getMaxPerPage()));
-    }
-
-    public function haveToPaginate()
-    {
-        if ($this->getExecuted()) {
-            return $this->getNumResults() > $this->getMaxPerPage();
-        }
-
-        throw new IPF_ORM_Exception(
-            'Cannot know if it is necessary to paginate a not yet executed Pager query'
-        );
-    }
-
-    public function setPage($page)
-    {
-        $this->_setPage($page);
-        $this->_setExecuted(false);
-    }
-
-    private function _setPage($page)
-    {
-        $page = intval($page);
-        $this->_page = ($page <= 0) ? 1 : $page;
-    }
-
-    public function getMaxPerPage()
-    {
-        return $this->_maxPerPage;
-    }
-
-    public function setMaxPerPage($max)
-    {
-        if ($max > 0) {
-            $this->_maxPerPage = $max;
-        } else if ($max == 0) {
-            $this->_maxPerPage = 25;
-        } else {
-            $this->_maxPerPage = abs($max);
-        }
-
-        $this->_setExecuted(false);
-    }
-
-    public function getResultsInPage()
-    {
-        $page = $this->getPage();
-
-        if ($page != $this->getLastPage()) {
-            return $this->getMaxPerPage();
-        }
-
-        $offset = ($this->getPage() - 1) * $this->getMaxPerPage();
-
-        return abs($this->getNumResults() - $offset);
-    }
-
-    public function getQuery()
-    {
-        return $this->_query;
-    }
-
-    protected function _setQuery($query)
-    {
-        if (is_string($query)) {
-            $query = IPF_ORM_Query::create()->parseQuery($query);
-        }
-
-        $this->_query = $query;
-    }
-
-    public function getCountQuery()
-    {
-        return ($this->_countQuery !== null) ? $this->_countQuery : $this->_query;
-    }
-
-    public function setCountQuery($query, $params = null)
-    {
-        if (is_string($query)) {
-            $query = IPF_ORM_Query::create()->parseQuery($query);
-        }
-
-        $this->_countQuery = $query;
-
-        $this->setCountQueryParams($params);
-
-        $this->_setExecuted(false);
-    }
-
-    public function getCountQueryParams($defaultParams = array())
-    {
-        return ($this->_countQueryParams !== null) ? $this->_countQueryParams : $defaultParams;
-    }
-
-    public function setCountQueryParams($params = array(), $append = false)
-    {
-        if ($append && is_array($this->_countQueryParams)) {
-            $this->_countQueryParams = array_merge($this->_countQueryParams, $params);
-        } else {
-            if ($params !== null && !is_array($params)) {
-                $params = array($params);
-            }
-
-            $this->_countQueryParams = $params;
-        }
-
-        $this->_setExecuted(false);
-    }
-
-    public function execute($params = array(), $hydrationMode = IPF_ORM::FETCH_RECORD)
-    {
-        if (!$this->getExecuted()) {
-            $this->_initialize($params);
-        }
-        return $this->getQuery()->execute($params, $hydrationMode);
-    }
-
-    public function __debugInfo()
-    {
-        return array(
-            'page' => $this->getPage(),
-            'size' => $this->getMaxPerPage(),
-            'query' => $this->getQuery(),
-        );
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/pager/layout.php b/ipf/legacy_orm/orm/pager/layout.php
deleted file mode 100644 (file)
index a5c1a32..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-<?php
-
-class IPF_ORM_Pager_Layout
-{
-    private $_pager;
-    private $_pagerRange;
-    private $_template;
-    private $_selectedTemplate;
-    private $_separatorTemplate;
-    private $_urlMask;
-    private $_maskReplacements = array();
-
-    public function __construct($pager, $pagerRange, $urlMask)
-    {
-        $this->_setPager($pager);
-        $this->_setPagerRange($pagerRange);
-        $this->_setUrlMask($urlMask);
-
-        $this->setTemplate('[<a href="{%url}">{%page}</a>]');
-        $this->setSelectedTemplate('');
-        $this->setSeparatorTemplate('');
-    }
-
-    public function getPager()
-    {
-        return $this->_pager;
-    }
-
-    protected function _setPager($pager)
-    {
-        $this->_pager = $pager;
-    }
-
-    public function execute($params = array(), $hydrationMode = IPF_ORM::FETCH_RECORD)
-    {
-        return $this->getPager()->execute($params, $hydrationMode);
-    }
-
-    public function getPagerRange()
-    {
-        return $this->_pagerRange;
-    }
-
-    protected function _setPagerRange($pagerRange)
-    {
-        $this->_pagerRange = $pagerRange;
-        $this->getPagerRange()->setPager($this->getPager());
-    }
-
-    public function getUrlMask()
-    {
-        return $this->_urlMask;
-    }
-
-    protected function _setUrlMask($urlMask)
-    {
-        $this->_urlMask = $urlMask;
-    }
-
-    public function getTemplate()
-    {
-        return $this->_template;
-    }
-
-    public function setTemplate($template)
-    {
-        $this->_template = $template;
-    }
-
-    public function getSelectedTemplate()
-    {
-        return $this->_selectedTemplate;
-    }
-
-    public function setSelectedTemplate($selectedTemplate)
-    {
-        $this->_selectedTemplate = $selectedTemplate;
-    }
-
-    public function getSeparatorTemplate()
-    {
-        return $this->_separatorTemplate;
-    }
-
-    public function setSeparatorTemplate($separatorTemplate)
-    {
-        $this->_separatorTemplate = $separatorTemplate;
-    }
-
-    public function addMaskReplacement($oldMask, $newMask, $asValue = false)
-    {
-        if (($oldMask = trim($oldMask)) != 'page_number') {
-            $this->_maskReplacements[$oldMask] = array(
-                'newMask' => $newMask,
-                'asValue' => ($asValue === false) ? false : true
-            );
-        }
-    }
-
-    public function removeMaskReplacement($oldMask)
-    {
-        if (isset($this->_maskReplacements[$oldMask])) {
-            $this->_maskReplacements[$oldMask] = null;
-            unset($this->_maskReplacements[$oldMask]);
-        }
-    }
-    
-    public function cleanMaskReplacements()
-    {
-        $this->_maskReplacements = null;
-        $this->_maskReplacements = array();
-    }
-
-    public function display($options = array(), $return = true)
-    {
-        $range = $this->getPagerRange()->rangeAroundPage();
-        $str = '';
-
-        // For each page in range
-        for ($i = 0, $l = count($range); $i < $l; $i++) {
-            // Define some optional mask values
-            $options['page_number'] = $range[$i];
-
-            $str .= $this->processPage($options);
-
-            // Apply separator between pages
-            if ($i < $l - 1) {
-                $str .= $this->getSeparatorTemplate();
-            }
-        }
-
-        // Possible wish to return value instead of print it on screen
-        if ($return) {
-            return $str;
-        }
-
-        echo $str;
-    }
-
-    public function processPage($options = array())
-    {
-        // Check if at least basic options are defined
-        if (!isset($options['page_number'])) {
-            throw new IPF_ORM_Exception(
-                'Cannot process template of the given page. ' .
-                'Missing at least one of needed parameters: \'page\' or \'page_number\''
-            );
-
-            // Should never reach here
-            return '';
-        }
-
-        // Assign "page" options index if not defined yet
-        if (!isset($this->_maskReplacements['page']) && !isset($options['page'])) {
-            $options['page'] = $options['page_number'];
-        }
-
-        return $this->_parseTemplate($options);
-    }
-
-    public function __toString()
-    {
-      return $this->display(array(), true);
-    }
-
-    protected function _parseTemplate($options = array())
-    {
-        $str = $this->_parseUrlTemplate($options);
-        $replacements = $this->_parseReplacementsTemplate($options);
-
-        return strtr($str, $replacements);
-    }
-
-    protected function _parseUrlTemplate($options = array())
-    {
-        $str = '';
-
-        // If given page is the current active one
-        if ($options['page_number'] == $this->getPager()->getPage()) {
-            $str = $this->_parseMaskReplacements($this->getSelectedTemplate());
-        }
-
-        // Possible attempt where Selected == Template
-        if ($str == '') {
-            $str = $this->_parseMaskReplacements($this->getTemplate());
-        }
-
-        return $str;
-    }
-
-    protected function _parseReplacementsTemplate($options = array())
-    {
-        // Defining "url" options index to allow {%url} mask
-        $options['url'] = $this->_parseUrl($options);
-
-        $replacements = array();
-
-        foreach ($options as $k => $v) {
-            $replacements['{%'.$k.'}'] = $v;
-        }
-
-        return $replacements;
-    }
-
-    protected function _parseUrl($options = array())
-    {
-        $str = $this->_parseMaskReplacements($this->getUrlMask());
-
-        $replacements = array();
-
-        foreach ($options as $k => $v) {
-            $replacements['{%'.$k.'}'] = $v;
-        }
-
-        return strtr($str, $replacements);
-    }
-
-    protected function _parseMaskReplacements($str)
-    {
-        $replacements = array();
-
-        foreach ($this->_maskReplacements as $k => $v) {
-            $replacements['{%'.$k.'}'] = ($v['asValue'] === true) ? $v['newMask'] : '{%'.$v['newMask'].'}';
-        }
-
-        return strtr($str, $replacements);
-    }
-
-    public function __debugInfo()
-    {
-        return array(
-            'pager' => $this->getPager()
-        );
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/pager/layoutarrows.php b/ipf/legacy_orm/orm/pager/layoutarrows.php
deleted file mode 100644 (file)
index 57c54d0..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-class IPF_ORM_Pager_LayoutArrows extends IPF_ORM_Pager_Layout
-{
-    public function display($options = array(), $return = true)
-    {
-        $pager = $this->getPager();
-        $str = '';
-
-        $range = $this->getPagerRange()->rangeAroundPage();
-
-        if ($pager->getFirstPage() != $pager->getLastPage())
-        {
-            $this->removeMaskReplacement('page');
-
-            if ($range[0] > 1)
-            {
-                $options['page_number'] = 1;
-                $str .= $this->processPage($options);
-
-                if ($range[0] > 2)
-                {
-                    $options['page_number'] = 2;
-                    $str .= $this->processPage($options);
-                }
-
-                if ($range[0]>3)
-                    $str .= ' ... ';
-            }
-
-            // Pages listing
-            $str .= parent::display($options, true);
-
-            $range = $this->getPagerRange()->rangeAroundPage();
-            $last_range = $range[count($range)-1];
-
-            if ($last_range < $pager->getLastPage())
-            {
-                if ($last_range < $pager->getLastPage() - 2)
-                {
-                    $str .= ' ... ';
-                }
-
-                if ($last_range < $pager->getLastPage() - 1)
-                {
-                    $options['page_number'] = $pager->getLastPage() - 1;
-                    $str .= $this->processPage($options);
-                }
-
-                if ($last_range < $pager->getLastPage())
-                {
-                    $options['page_number'] = $pager->getLastPage();
-                    $str .= $this->processPage($options);
-                }
-            }
-        }
-
-        if ($return)
-            return $str;
-
-        echo $str;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/pager/range.php b/ipf/legacy_orm/orm/pager/range.php
deleted file mode 100644 (file)
index 18ffd8f..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-
-abstract class IPF_ORM_Pager_Range
-{
-    protected $_options;
-    private $pager;
-
-    final public function __construct($options = array(), $pager = null)
-    {
-        $this->_setOptions($options);
-
-        if ($pager !== null) {
-            $this->setPager($pager);
-        }
-    }
-
-    public function getPager()
-    {
-        return $this->pager;
-    }
-
-    public function setPager($pager)
-    {
-        $this->pager = $pager;
-
-        // Lazy-load initialization. It only should be called when all
-        // needed information data is ready (this can only happens when we have
-        // options stored and a IPF_ORM_Pager assocated)
-        $this->_initialize();
-    }
-
-    public function getOptions()
-    {
-        return $this->_options;
-    }
-
-    public function getOption($option)
-    {
-        if (isset($this->_options[$option])) {
-            return $this->_options[$option];
-        }
-
-        throw new IPF_ORM_Exception(
-            'Cannot access unexistent option \'' . $option . '\' in IPF_ORM_Pager_Range class'
-        );
-    }
-
-    protected function _setOptions($options)
-    {
-        $this->_options = $options;
-    }
-
-    public function isInRange($page)
-    {
-        return (array_search($page, $this->rangeAroundPage()) !== false);
-    }
-
-    abstract protected function _initialize();
-
-    abstract public function rangeAroundPage();
-}
diff --git a/ipf/legacy_orm/orm/pager/range/jumping.php b/ipf/legacy_orm/orm/pager/range/jumping.php
deleted file mode 100644 (file)
index 4324921..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-class IPF_ORM_Pager_Range_Jumping extends IPF_ORM_Pager_Range
-{
-    private $_chunkLength;
-
-    protected function _initialize()
-    {
-        if (isset($this->_options['chunk'])) {
-            $this->_setChunkLength($this->_options['chunk']);
-        } else {
-            throw new IPF_ORM_Exception('Missing parameter \'chunk\' that must be define in options.');
-        }
-    }
-
-    public function getChunkLength()
-    {
-        return $this->_chunkLength;
-    }
-
-    protected function _setChunkLength($chunkLength)
-    {
-        $this->_chunkLength = $chunkLength;
-    }
-
-    public function rangeAroundPage()
-    {
-        $pager = $this->getPager();
-
-        if ($pager->getExecuted()) {
-            $page = $pager->getPage();
-
-            // Define initial assignments for StartPage and EndPage
-            $startPage = $page - ($page - 1) % $this->getChunkLength();
-            $endPage = ($startPage + $this->getChunkLength()) - 1;
-
-            // Check for EndPage out-range
-            if ($endPage > $pager->getLastPage()) {
-                $endPage = $pager->getLastPage();
-            }
-
-            // No need to check for out-range in start, it will never happens
-
-            return range($startPage, $endPage);
-        }
-
-        throw new IPF_ORM_Exception(
-            'Cannot retrieve the range around the page of a not yet executed Pager query'
-        );
-    }
-}
diff --git a/ipf/legacy_orm/orm/pager/range/sliding.php b/ipf/legacy_orm/orm/pager/range/sliding.php
deleted file mode 100644 (file)
index cf8198e..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-<?php
-
-class IPF_ORM_Pager_Range_Sliding extends IPF_ORM_Pager_Range
-{
-    private $_chunkLength;
-
-    protected function _initialize()
-    {
-        if (isset($this->_options['chunk'])) {
-            $this->_setChunkLength($this->_options['chunk']);
-        } else {
-            throw new IPF_ORM_Exception('Missing parameter \'chunk\' that must be defined in options.');
-        }
-    }
-
-    public function getChunkLength()
-    {
-        return $this->_chunkLength;
-    }
-
-    protected function _setChunkLength($chunkLength)
-    {
-        $chunkLength = (int) $chunkLength;
-        if (!$chunkLength) {
-            $chunkLength = 1;
-        } else {
-            $this->_chunkLength = $chunkLength;
-        }
-    }
-
-    public function rangeAroundPage()
-    {
-        $pager = $this->getPager();
-
-        if ($pager->getExecuted()) {
-            $page  = $pager->getPage();
-            $pages = $pager->getLastPage();
-
-            $chunk = $this->getChunkLength();
-
-            if ($chunk > $pages) {
-                $chunk = $pages;
-            }
-
-            $chunkStart = $page - (floor($chunk / 2));
-            $chunkEnd   = $page + (ceil($chunk / 2)-1);
-
-            if ($chunkStart < 1) {
-                $adjust = 1 - $chunkStart;
-                $chunkStart = 1;
-                $chunkEnd = $chunkEnd + $adjust;
-            }
-
-            if ($chunkEnd > $pages) {
-                $adjust = $chunkEnd - $pages;
-                $chunkStart = $chunkStart - $adjust;
-                $chunkEnd = $pages;
-            }
-            return range($chunkStart, $chunkEnd);
-        }
-
-        throw new IPF_ORM_Exception(
-            'Cannot retrieve the range around the page of a not yet executed Pager query'
-        );
-    }
-}
diff --git a/ipf/legacy_orm/orm/query.php b/ipf/legacy_orm/orm/query.php
deleted file mode 100644 (file)
index 1830c33..0000000
+++ /dev/null
@@ -1,1259 +0,0 @@
-<?php
-
-class IPF_ORM_Query extends IPF_ORM_Query_Abstract implements Countable, Serializable
-{
-    protected static $_keywords  = array('ALL',
-                                         'AND',
-                                         'ANY',
-                                         'AS',
-                                         'ASC',
-                                         'AVG',
-                                         'BETWEEN',
-                                         'BIT_LENGTH',
-                                         'BY',
-                                         'CHARACTER_LENGTH',
-                                         'CHAR_LENGTH',
-                                         'CURRENT_DATE',
-                                         'CURRENT_TIME',
-                                         'CURRENT_TIMESTAMP',
-                                         'DELETE',
-                                         'DESC',
-                                         'DISTINCT',
-                                         'EMPTY',
-                                         'EXISTS',
-                                         'FALSE',
-                                         'FETCH',
-                                         'FROM',
-                                         'GROUP',
-                                         'HAVING',
-                                         'IN',
-                                         'INDEXBY',
-                                         'INNER',
-                                         'IS',
-                                         'JOIN',
-                                         'LEFT',
-                                         'LIKE',
-                                         'LOWER',
-                                         'MEMBER',
-                                         'MOD',
-                                         'NEW',
-                                         'NOT',
-                                         'NULL',
-                                         'OBJECT',
-                                         'OF',
-                                         'OR',
-                                         'ORDER',
-                                         'OUTER',
-                                         'POSITION',
-                                         'SELECT',
-                                         'SOME',
-                                         'TRIM',
-                                         'TRUE',
-                                         'UNKNOWN',
-                                         'UPDATE',
-                                         'WHERE');
-
-    protected $_subqueryAliases = array();
-    protected $_aggregateAliasMap      = array();
-    protected $_pendingAggregates = array();
-    protected $_isSubquery;
-    protected $_neededTables = array();
-    protected $_pendingSubqueries = array();
-    protected $_pendingFields = array();
-    protected $_parsers = array();
-    protected $_pendingJoinConditions = array();
-    protected $_expressionMap = array();
-    protected $_sql;
-
-    public static function create($conn = null)
-    {
-        return new IPF_ORM_Query($conn);
-    }
-
-    public function reset()
-    {
-        $this->_pendingJoinConditions = array();
-        $this->_pendingSubqueries = array();
-        $this->_pendingFields = array();
-        $this->_neededTables = array();
-        $this->_expressionMap = array();
-        $this->_subqueryAliases = array();
-    }
-
-    public function createSubquery()
-    {
-        $class = get_class($this);
-        $obj   = new $class();
-
-        // copy the aliases to the subquery
-        $obj->copyAliases($this);
-
-        // this prevents the 'id' being selected, re ticket #307
-        $obj->isSubquery(true);
-
-        return $obj;
-    }
-
-    protected function _addPendingJoinCondition($componentAlias, $joinCondition)
-    {
-        $this->_pendingJoins[$componentAlias] = $joinCondition;
-    }
-
-    public function addEnumParam($key, $table = null, $column = null)
-    {
-        $array = (isset($table) || isset($column)) ? array($table, $column) : array();
-
-        if ($key === '?') {
-            $this->_enumParams[] = $array;
-        } else {
-            $this->_enumParams[$key] = $array;
-        }
-    }
-
-    public function getEnumParams()
-    {
-        return $this->_enumParams;
-    }
-
-    public function getDql()
-    {
-        $q = '';
-        $q .= ( ! empty($this->_dqlParts['select']))?  'SELECT '    . implode(', ', $this->_dqlParts['select']) : '';
-        $q .= ( ! empty($this->_dqlParts['from']))?    ' FROM '     . implode(' ', $this->_dqlParts['from']) : '';
-        $q .= ( ! empty($this->_dqlParts['where']))?   ' WHERE '    . implode(' AND ', $this->_dqlParts['where']) : '';
-        $q .= ( ! empty($this->_dqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_dqlParts['groupby']) : '';
-        $q .= ( ! empty($this->_dqlParts['having']))?  ' HAVING '   . implode(' AND ', $this->_dqlParts['having']) : '';
-        $q .= ( ! empty($this->_dqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_dqlParts['orderby']) : '';
-        $q .= ( ! empty($this->_dqlParts['limit']))?   ' LIMIT '    . implode(' ', $this->_dqlParts['limit']) : '';
-        $q .= ( ! empty($this->_dqlParts['offset']))?  ' OFFSET '   . implode(' ', $this->_dqlParts['offset']) : '';
-
-        return $q;
-    }
-
-
-    public function fetchArray($params = array()) {
-        return $this->execute($params, IPF_ORM::HYDRATE_ARRAY);
-    }
-
-    public function fetchOne($params = array(), $hydrationMode = null)
-    {
-        $collection = $this->execute($params, $hydrationMode);
-
-        if (count($collection) === 0) {
-            return false;
-        }
-
-        if ($collection instanceof IPF_ORM_Collection) {
-            return $collection->getFirst();
-        } else if (is_array($collection)) {
-            return array_shift($collection);
-        }
-
-        return false;
-    }
-
-    public function isSubquery($bool = null)
-    {
-        if ($bool === null) {
-            return $this->_isSubquery;
-        }
-
-        $this->_isSubquery = (bool) $bool;
-        return $this;
-    }
-
-    public function getAggregateAlias($dqlAlias)
-    {
-        return $this->getSqlAggregateAlias($dqlAlias);
-    }
-
-    public function getSqlAggregateAlias($dqlAlias)
-    {
-        if (isset($this->_aggregateAliasMap[$dqlAlias])) {
-            // mark the expression as used
-            $this->_expressionMap[$dqlAlias][1] = true;
-
-            return $this->_aggregateAliasMap[$dqlAlias];
-        } else if ( ! empty($this->_pendingAggregates)) {
-            $this->processPendingAggregates();
-
-            return $this->getSqlAggregateAlias($dqlAlias);
-        } else {
-            throw new IPF_ORM_Exception('Unknown aggregate alias: ' . $dqlAlias);
-        }
-    }
-
-    public function getDqlPart($queryPart)
-    {
-        if ( ! isset($this->_dqlParts[$queryPart])) {
-           throw new IPF_ORM_Exception('Unknown query part ' . $queryPart);
-        }
-
-        return $this->_dqlParts[$queryPart];
-    }
-
-    public function contains($dql)
-    {
-      return stripos($this->getDql(), $dql) === false ? false : true;
-    }
-
-    public function processPendingFields($componentAlias)
-    {
-        $tableAlias = $this->getTableAlias($componentAlias);
-        $table      = $this->_queryComponents[$componentAlias]['table'];
-
-        if ( ! isset($this->_pendingFields[$componentAlias])) {
-            if ($this->_hydrator->getHydrationMode() != IPF_ORM::HYDRATE_NONE) {
-                if ( ! $this->_isSubquery && $componentAlias == $this->getRootAlias()) {
-                    throw new IPF_ORM_Exception("The root class of the query (alias $componentAlias) "
-                            . " must have at least one field selected.");
-                }
-            }
-            return;
-        }
-
-        // At this point we know the component is FETCHED (either it's the base class of
-        // the query (FROM xyz) or its a "fetch join").
-
-        // Check that the parent join (if there is one), is a "fetch join", too.
-        if (isset($this->_queryComponents[$componentAlias]['parent'])) {
-            $parentAlias = $this->_queryComponents[$componentAlias]['parent'];
-            if (is_string($parentAlias) && ! isset($this->_pendingFields[$parentAlias])
-                    && $this->_hydrator->getHydrationMode() != IPF_ORM::HYDRATE_NONE) {
-                throw new IPF_ORM_Exception("The left side of the join between "
-                        . "the aliases '$parentAlias' and '$componentAlias' must have at least"
-                        . " the primary key field(s) selected.");
-            }
-        }
-
-        $fields = $this->_pendingFields[$componentAlias];
-
-        // check for wildcards
-        if (in_array('*', $fields)) {
-            $fields = $table->getFieldNames();
-        } else {
-            // only auto-add the primary key fields if this query object is not
-            // a subquery of another query object
-            if ( ! $this->_isSubquery || $this->_hydrator->getHydrationMode() === IPF_ORM::HYDRATE_NONE) {
-                $fields = array_unique(array_merge((array) $table->getIdentifier(), $fields));
-            }
-        }
-
-        $sql = array();
-        foreach ($fields as $fieldName) {
-            $columnName = $table->getColumnName($fieldName);
-            $sql[] = $this->_conn->quoteIdentifier($tableAlias . '.' . $columnName)
-                   . ' AS '
-                   . $this->_conn->quoteIdentifier($tableAlias . '__' . $columnName);
-        }
-
-        $this->_neededTables[] = $tableAlias;
-
-        return implode(', ', $sql);
-    }
-
-    public function parseSelectField($field)
-    {
-        $terms = explode('.', $field);
-
-        if (isset($terms[1])) {
-            $componentAlias = $terms[0];
-            $field = $terms[1];
-        } else {
-            reset($this->_queryComponents);
-            $componentAlias = key($this->_queryComponents);
-            $fields = $terms[0];
-        }
-
-        $tableAlias = $this->getTableAlias($componentAlias);
-        $table      = $this->_queryComponents[$componentAlias]['table'];
-
-
-        // check for wildcards
-        if ($field === '*') {
-            $sql = array();
-
-            foreach ($table->getColumnNames() as $field) {
-                $sql[] = $this->parseSelectField($componentAlias . '.' . $field);
-            }
-
-            return implode(', ', $sql);
-        } else {
-            $name = $table->getColumnName($field);
-
-            $this->_neededTables[] = $tableAlias;
-
-            return $this->_conn->quoteIdentifier($tableAlias . '.' . $name)
-                   . ' AS '
-                   . $this->_conn->quoteIdentifier($tableAlias . '__' . $name);
-        }
-    }
-
-    public function getExpressionOwner($expr)
-    {
-        if (strtoupper(substr(trim($expr, '( '), 0, 6)) !== 'SELECT') {
-            preg_match_all("/[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*[\.[a-z0-9]+]*/i", $expr, $matches);
-
-            $match = current($matches);
-
-            if (isset($match[0])) {
-                $terms = explode('.', $match[0]);
-
-                return $terms[0];
-            }
-        }
-        return $this->getRootAlias();
-
-    }
-
-    public function parseSelect($dql)
-    {
-        $refs = $this->_tokenizer->sqlExplode($dql, ',');
-
-        $pos   = strpos(trim($refs[0]), ' ');
-        $first = substr($refs[0], 0, $pos);
-
-        // check for DISTINCT keyword
-        if ($first === 'DISTINCT') {
-            $this->_sqlParts['distinct'] = true;
-
-            $refs[0] = substr($refs[0], ++$pos);
-        }
-
-        $parsedComponents = array();
-
-        foreach ($refs as $reference) {
-            $reference = trim($reference);
-
-            if (empty($reference)) {
-                continue;
-            }
-
-            $terms = $this->_tokenizer->sqlExplode($reference, ' ');
-
-            $pos   = strpos($terms[0], '(');
-
-            if (count($terms) > 1 || $pos !== false) {
-                $expression = array_shift($terms);
-                $alias = array_pop($terms);
-
-                if ( ! $alias) {
-                    $alias = substr($expression, 0, $pos);
-                }
-
-                $componentAlias = $this->getExpressionOwner($expression);
-                $expression = $this->parseClause($expression);
-
-                $tableAlias = $this->getTableAlias($componentAlias);
-
-                $index    = count($this->_aggregateAliasMap);
-
-                $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index);
-
-                $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias;
-
-                $this->_aggregateAliasMap[$alias] = $sqlAlias;
-                $this->_expressionMap[$alias][0] = $expression;
-
-                $this->_queryComponents[$componentAlias]['agg'][$index] = $alias;
-
-                $this->_neededTables[] = $tableAlias;
-            } else {
-                $e = explode('.', $terms[0]);
-
-                if (isset($e[1])) {
-                    $componentAlias = $e[0];
-                    $field = $e[1];
-                } else {
-                    reset($this->_queryComponents);
-                    $componentAlias = key($this->_queryComponents);
-                    $field = $e[0];
-                }
-
-                $this->_pendingFields[$componentAlias][] = $field;
-            }
-        }
-    }
-
-    public function parseClause($clause)
-    {
-       $clause = trim($clause);
-
-       if (is_numeric($clause)) {
-           return $clause;
-       }
-
-        $terms = $this->_tokenizer->clauseExplode($clause, array(' ', '+', '-', '*', '/', '<', '>', '=', '>=', '<='));
-
-        $str = '';
-        foreach ($terms as $term) {
-            $pos = strpos($term[0], '(');
-
-            if ($pos !== false) {
-                $name = substr($term[0], 0, $pos);
-                $term[0] = $this->parseFunctionExpression($term[0]);
-            } else {
-                if (substr($term[0], 0, 1) !== "'" && substr($term[0], -1) !== "'") {
-
-                    if (strpos($term[0], '.') !== false) {
-                        if ( ! is_numeric($term[0])) {
-                            $e = explode('.', $term[0]);
-
-                            $field = array_pop($e);
-
-                            if ($this->getType() === IPF_ORM_Query::SELECT) {
-                                $componentAlias = implode('.', $e);
-
-                                if (empty($componentAlias)) {
-                                    $componentAlias = $this->getRootAlias();
-                                }
-
-                                $this->load($componentAlias);
-
-                                // check the existence of the component alias
-                                if ( ! isset($this->_queryComponents[$componentAlias])) {
-                                    throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias);
-                                }
-
-                                $table = $this->_queryComponents[$componentAlias]['table'];
-
-                                $def = $table->getDefinitionOf($field);
-
-                                // get the actual field name from alias
-                                $field = $table->getColumnName($field);
-
-                                // check column existence
-                                if ( ! $def) {
-                                    throw new IPF_ORM_Exception('Unknown column ' . $field);
-                                }
-
-                                if (isset($def['owner'])) {
-                                    $componentAlias = $componentAlias . '.' . $def['owner'];
-                                }
-
-                                $tableAlias = $this->getTableAlias($componentAlias);
-
-                                // build sql expression
-                                $term[0] = $this->_conn->quoteIdentifier($tableAlias)
-                                         . '.'
-                                         . $this->_conn->quoteIdentifier($field);
-                            } else {
-                                // build sql expression
-                                $field = $this->getRoot()->getColumnName($field);
-                                $term[0] = $this->_conn->quoteIdentifier($field);
-                            }
-                        }
-                    } else {
-                        if ( ! empty($term[0]) &&
-                             ! in_array(strtoupper($term[0]), self::$_keywords) &&
-                             ! is_numeric($term[0])) {
-
-                            $componentAlias = $this->getRootAlias();
-
-                            $found = false;
-
-                            if ($componentAlias !== false &&
-                                $componentAlias !== null) {
-                                $table = $this->_queryComponents[$componentAlias]['table'];
-
-                                // check column existence
-                                if ($table->hasField($term[0])) {
-                                    $found = true;
-
-                                    $def = $table->getDefinitionOf($term[0]);
-
-                                    // get the actual column name from field name
-                                    $term[0] = $table->getColumnName($term[0]);
-
-
-                                    if (isset($def['owner'])) {
-                                        $componentAlias = $componentAlias . '.' . $def['owner'];
-                                    }
-
-                                    $tableAlias = $this->getTableAlias($componentAlias);
-
-                                    if ($this->getType() === IPF_ORM_Query::SELECT) {
-                                        // build sql expression
-                                        $term[0] = $this->_conn->quoteIdentifier($tableAlias)
-                                                 . '.'
-                                                 . $this->_conn->quoteIdentifier($term[0]);
-                                    } else {
-                                        // build sql expression
-                                        $term[0] = $this->_conn->quoteIdentifier($term[0]);
-                                    }
-                                } else {
-                                    $found = false;
-                                }
-                            }
-
-                            if ( ! $found) {
-                                $term[0] = $this->getSqlAggregateAlias($term[0]);
-                            }
-                        }
-                    }
-                }
-            }
-
-            $str .= $term[0] . $term[1];
-        }
-        return $str;
-    }
-
-    public function parseIdentifierReference($expr)
-    {
-    }
-
-    public function parseFunctionExpression($expr)
-    {
-        $pos = strpos($expr, '(');
-
-        $name = substr($expr, 0, $pos);
-
-        if ($name === '') {
-            return $this->parseSubquery($expr);
-        }
-
-        $argStr = substr($expr, ($pos + 1), -1);
-
-        $args   = array();
-        // parse args
-
-        foreach ($this->_tokenizer->sqlExplode($argStr, ',') as $arg) {
-           $args[] = $this->parseClause($arg);
-        }
-
-        // convert DQL function to its RDBMS specific equivalent
-        try {
-            $expr = call_user_func_array(array($this->_conn->expression, $name), $args);
-        } catch (IPF_ORM_Exception $e) {
-            throw new IPF_ORM_Exception('Unknown function ' . $name . '.');
-        }
-
-        return $expr;
-    }
-
-    public function parseSubquery($subquery)
-    {
-        $trimmed = trim($this->_tokenizer->bracketTrim($subquery));
-
-        // check for possible subqueries
-        if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') {
-            // parse subquery
-            $trimmed = $this->createSubquery()->parseDqlQuery($trimmed)->getQuery();
-        } else {
-            // parse normal clause
-            $trimmed = $this->parseClause($trimmed);
-        }
-
-        return '(' . $trimmed . ')';
-    }
-
-    public function processPendingSubqueries()
-    {
-        foreach ($this->_pendingSubqueries as $value) {
-            list($dql, $alias) = $value;
-
-            $subquery = $this->createSubquery();
-
-            $sql = $subquery->parseDqlQuery($dql, false)->getQuery();
-
-            reset($this->_queryComponents);
-            $componentAlias = key($this->_queryComponents);
-            $tableAlias = $this->getTableAlias($componentAlias);
-
-            $sqlAlias = $tableAlias . '__' . count($this->_aggregateAliasMap);
-
-            $this->_sqlParts['select'][] = '(' . $sql . ') AS ' . $this->_conn->quoteIdentifier($sqlAlias);
-
-            $this->_aggregateAliasMap[$alias] = $sqlAlias;
-            $this->_queryComponents[$componentAlias]['agg'][] = $alias;
-        }
-        $this->_pendingSubqueries = array();
-    }
-
-    public function processPendingAggregates()
-    {
-        // iterate trhough all aggregates
-        foreach ($this->_pendingAggregates as $aggregate) {
-            list ($expression, $components, $alias) = $aggregate;
-
-            $tableAliases = array();
-
-            // iterate through the component references within the aggregate function
-            if ( ! empty ($components)) {
-                foreach ($components as $component) {
-
-                    if (is_numeric($component)) {
-                        continue;
-                    }
-
-                    $e = explode('.', $component);
-
-                    $field = array_pop($e);
-                    $componentAlias = implode('.', $e);
-
-                    // check the existence of the component alias
-                    if ( ! isset($this->_queryComponents[$componentAlias])) {
-                        throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias);
-                    }
-
-                    $table = $this->_queryComponents[$componentAlias]['table'];
-
-                    $field = $table->getColumnName($field);
-
-                    // check column existence
-                    if ( ! $table->hasColumn($field)) {
-                        throw new IPF_ORM_Exception('Unknown column ' . $field);
-                    }
-
-                    $sqlTableAlias = $this->getSqlTableAlias($componentAlias);
-
-                    $tableAliases[$sqlTableAlias] = true;
-
-                    // build sql expression
-
-                    $identifier = $this->_conn->quoteIdentifier($sqlTableAlias . '.' . $field);
-                    $expression = str_replace($component, $identifier, $expression);
-                }
-            }
-
-            if (count($tableAliases) !== 1) {
-                $componentAlias = reset($this->_tableAliasMap);
-                $tableAlias = key($this->_tableAliasMap);
-            }
-
-            $index    = count($this->_aggregateAliasMap);
-            $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index);
-
-            $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias;
-
-            $this->_aggregateAliasMap[$alias] = $sqlAlias;
-            $this->_expressionMap[$alias][0] = $expression;
-
-            $this->_queryComponents[$componentAlias]['agg'][$index] = $alias;
-
-            $this->_neededTables[] = $tableAlias;
-        }
-        // reset the state
-        $this->_pendingAggregates = array();
-    }
-
-    protected function _buildSqlQueryBase()
-    {
-        switch ($this->_type) {
-            case self::DELETE:
-                $q = 'DELETE FROM ';
-            break;
-            case self::UPDATE:
-                $q = 'UPDATE ';
-            break;
-            case self::SELECT:
-                $distinct = ($this->_sqlParts['distinct']) ? 'DISTINCT ' : '';
-                $q = 'SELECT ' . $distinct . implode(', ', $this->_sqlParts['select']) . ' FROM ';
-            break;
-        }
-        return $q;
-    }
-
-    protected function _buildSqlFromPart()
-    {
-        $q = '';
-        foreach ($this->_sqlParts['from'] as $k => $part) {
-            if ($k === 0) {
-                $q .= $part;
-                continue;
-            }
-
-            // preserve LEFT JOINs only if needed
-            // Check if it's JOIN, if not add a comma separator instead of space
-            if (!preg_match('/\bJOIN\b/i', $part) && !isset($this->_pendingJoinConditions[$k])) {
-                $q .= ', ' . $part;
-            } else {
-                $e = explode(' ', $part);
-
-                if (substr($part, 0, 9) === 'LEFT JOIN') {
-                    $aliases = array_merge($this->_subqueryAliases,
-                                array_keys($this->_neededTables));
-
-                    if ( ! in_array($e[3], $aliases) &&
-                        ! in_array($e[2], $aliases) &&
-
-                        ! empty($this->_pendingFields)) {
-                        continue;
-                    }
-
-                }
-
-                if (isset($this->_pendingJoinConditions[$k])) {
-                    $parser = new IPF_ORM_JoinCondition($this, $this->_tokenizer);
-
-                    if (strpos($part, ' ON ') !== false) {
-                        $part .= ' AND ';
-                    } else {
-                        $part .= ' ON ';
-                    }
-                    $part .= $parser->parse($this->_pendingJoinConditions[$k]);
-
-                    unset($this->_pendingJoinConditions[$k]);
-                }
-
-                $tableAlias = trim($e[3], '"');
-                $componentAlias = $this->getComponentAlias($tableAlias);
-
-                $string = $this->getInheritanceCondition($componentAlias);
-
-                if ($string) {
-                    $q .= ' ' . $part . ' AND ' . $string;
-                } else {
-                    $q .= ' ' . $part;
-                }
-            }
-
-            $this->_sqlParts['from'][$k] = $part;
-        }
-        return $q;
-    }
-
-    public function getSqlQuery($params = array())
-    {
-        if ($this->_state !== self::STATE_DIRTY) {
-           return $this->_sql;
-        }
-
-        // reset the state
-        if ( ! $this->isSubquery()) {
-            $this->_queryComponents = array();
-            $this->_pendingAggregates = array();
-            $this->_aggregateAliasMap = array();
-        }
-        $this->reset();
-
-        // invoke the preQuery hook
-        $this->_preQuery();
-
-        // process the DQL parts => generate the SQL parts.
-        // this will also populate the $_queryComponents.
-        foreach ($this->_dqlParts as $queryPartName => $queryParts) {
-            $this->_processDqlQueryPart($queryPartName, $queryParts);
-        }
-        $this->_state = self::STATE_CLEAN;
-
-        $params = $this->convertEnums($params);
-
-        // Proceed with the generated SQL
-
-        if (empty($this->_sqlParts['from'])) {
-            return false;
-        }
-
-        $map = reset($this->_queryComponents);
-        $table = $map['table'];
-        $rootAlias = key($this->_queryComponents);
-
-        $sql = array();
-        if ( ! empty($this->_pendingFields)) {
-            foreach ($this->_queryComponents as $alias => $map) {
-                $fieldSql = $this->processPendingFields($alias);
-                if ( ! empty($fieldSql)) {
-                    $sql[] = $fieldSql;
-                }
-            }
-        }
-        if ( ! empty($sql)) {
-            array_unshift($this->_sqlParts['select'], implode(', ', $sql));
-        }
-
-        $this->_pendingFields = array();
-
-        // build the basic query
-        $q  = $this->_buildSqlQueryBase();
-        $q .= $this->_buildSqlFromPart();
-
-        if ( ! empty($this->_sqlParts['set'])) {
-            $q .= ' SET ' . implode(', ', $this->_sqlParts['set']);
-        }
-
-        $string = $this->getInheritanceCondition($this->getRootAlias());
-
-        // apply inheritance to WHERE part
-        if ( ! empty($string)) {
-            if (substr($string, 0, 1) === '(' && substr($string, -1) === ')') {
-                $this->_sqlParts['where'][] = $string;
-            } else {
-                $this->_sqlParts['where'][] = '(' . $string . ')';
-            }
-        }
-
-        $q .= ( ! empty($this->_sqlParts['where']))?   ' WHERE '    . implode(' AND ', $this->_sqlParts['where']) : '';
-        $q .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby'])  : '';
-        $q .= ( ! empty($this->_sqlParts['having']))?  ' HAVING '   . implode(' AND ', $this->_sqlParts['having']): '';
-        $q .= ( ! empty($this->_sqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby'])  : '';
-
-        $q = $this->_conn->modifyLimitQuery($q, $this->_sqlParts['limit'], $this->_sqlParts['offset']);
-
-        // return to the previous state
-        if ( ! empty($string)) {
-            array_pop($this->_sqlParts['where']);
-        }
-        $this->_sql = $q;
-
-        return $q;
-    }
-
-    public function parseDqlQuery($query, $clear = true)
-    {
-        if ($clear) {
-            $this->clear();
-        }
-
-        $query = trim($query);
-        $query = str_replace("\r", "\n", str_replace("\r\n", "\n", $query));
-        $query = str_replace("\n", ' ', $query);
-
-        $parts = $this->_tokenizer->tokenizeQuery($query);
-
-        foreach ($parts as $partName => $subParts) {
-            $subParts = trim($subParts);
-            $partName = strtolower($partName);
-            switch ($partName) {
-                case 'create':
-                    $this->_type = self::CREATE;
-                break;
-                case 'insert':
-                    $this->_type = self::INSERT;
-                break;
-                case 'delete':
-                    $this->_type = self::DELETE;
-                break;
-                case 'select':
-                    $this->_type = self::SELECT;
-                    $this->_addDqlQueryPart($partName, $subParts);
-                break;
-                case 'update':
-                    $this->_type = self::UPDATE;
-                    $partName = 'from';
-                case 'from':
-                    $this->_addDqlQueryPart($partName, $subParts);
-                break;
-                case 'set':
-                    $this->_addDqlQueryPart($partName, $subParts, true);
-                break;
-                case 'group':
-                case 'order':
-                    $partName .= 'by';
-                case 'where':
-                case 'having':
-                case 'limit':
-                case 'offset':
-                    $this->_addDqlQueryPart($partName, $subParts);
-                break;
-            }
-        }
-
-        return $this;
-    }
-
-    public function load($path, $loadFields = true)
-    {
-        if (isset($this->_queryComponents[$path])) {
-            return $this->_queryComponents[$path];
-        }
-
-        $e = $this->_tokenizer->quoteExplode($path, ' INDEXBY ');
-
-        $mapWith = null;
-        if (count($e) > 1) {
-            $mapWith = trim($e[1]);
-
-            $path = $e[0];
-        }
-
-        // parse custom join conditions
-        $e = explode(' ON ', $path);
-
-        $joinCondition = '';
-
-        if (count($e) > 1) {
-            $joinCondition = $e[1];
-            $overrideJoin = true;
-            $path = $e[0];
-        } else {
-            $e = explode(' WITH ', $path);
-
-            if (count($e) > 1) {
-                $joinCondition = $e[1];
-                $path = $e[0];
-            }
-            $overrideJoin = false;
-        }
-
-        $tmp            = explode(' ', $path);
-        $componentAlias = $originalAlias = (count($tmp) > 1) ? end($tmp) : null;
-
-        $e = preg_split("/[.:]/", $tmp[0], -1);
-
-        $fullPath = $tmp[0];
-        $prevPath = '';
-        $fullLength = strlen($fullPath);
-
-        if (isset($this->_queryComponents[$e[0]])) {
-            $table = $this->_queryComponents[$e[0]]['table'];
-            $componentAlias = $e[0];
-
-            $prevPath = $parent = array_shift($e);
-        }
-
-        foreach ($e as $key => $name) {
-            // get length of the previous path
-            $length = strlen($prevPath);
-
-            // build the current component path
-            $prevPath = ($prevPath) ? $prevPath . '.' . $name : $name;
-
-            $delimeter = substr($fullPath, $length, 1);
-
-            // if an alias is not given use the current path as an alias identifier
-            if (strlen($prevPath) === $fullLength && isset($originalAlias)) {
-                $componentAlias = $originalAlias;
-            } else {
-                $componentAlias = $prevPath;
-            }
-
-            // if the current alias already exists, skip it
-            if (isset($this->_queryComponents[$componentAlias])) {
-                throw new IPF_ORM_Exception("Duplicate alias '$componentAlias' in query.");
-            }
-
-            if ( ! isset($table)) {
-                // process the root of the path
-
-                $table = $this->loadRoot($name, $componentAlias);
-            } else {
-                $join = ($delimeter == ':') ? 'INNER JOIN ' : 'LEFT JOIN ';
-
-                $relation = $table->getRelation($name);
-                $localTable = $table;
-
-                $table    = $relation->getTable();
-                $this->_queryComponents[$componentAlias] = array('table' => $table,
-                                                                 'parent'   => $parent,
-                                                                 'relation' => $relation,
-                                                                 'map'      => null);
-
-                $localAlias   = $this->getTableAlias($parent, $table->getTableName());
-                $foreignAlias = $this->getTableAlias($componentAlias, $relation->getTable()->getTableName());
-
-                $foreignSql   = $this->_conn->quoteIdentifier($relation->getTable()->getTableName())
-                              . ' '
-                              . $this->_conn->quoteIdentifier($foreignAlias);
-
-                $map = $relation->getTable()->inheritanceMap;
-
-                if ( ! $loadFields || ! empty($map) || $joinCondition) {
-                    $this->_subqueryAliases[] = $foreignAlias;
-                }
-
-                if ($relation instanceof IPF_ORM_Relation_Association) {
-                    $asf = $relation->getAssociationTable();
-
-                    $assocTableName = $asf->getTableName();
-
-                    if ( ! $loadFields || ! empty($map) || $joinCondition) {
-                        $this->_subqueryAliases[] = $assocTableName;
-                    }
-
-                    $assocPath = $prevPath . '.' . $asf->getComponentName();
-
-                    $this->_queryComponents[$assocPath] = array('parent' => $prevPath, 'relation' => $relation, 'table' => $asf);
-
-                    $assocAlias = $this->getTableAlias($assocPath, $asf->getTableName());
-
-                    $queryPart = $join . $assocTableName . ' ' . $assocAlias;
-
-                    $queryPart .= ' ON ' . $localAlias
-                                . '.'
-                                . $localTable->getColumnName($localTable->getIdentifier()) // what about composite keys?
-                                . ' = '
-                                . $assocAlias . '.' . $relation->getLocal();
-
-                    if ($relation->isEqual()) {
-                        // equal nest relation needs additional condition
-                        $queryPart .= ' OR ' . $localAlias
-                                    . '.'
-                                    . $table->getColumnName($table->getIdentifier())
-                                    . ' = '
-                                    . $assocAlias . '.' . $relation->getForeign();
-                    }
-
-                    $this->_sqlParts['from'][] = $queryPart;
-
-                    $queryPart = $join . $foreignSql;
-
-                    if ( ! $overrideJoin) {
-                        $queryPart .= $this->buildAssociativeRelationSql($relation, $assocAlias, $foreignAlias, $localAlias);
-                    }
-                } else {
-                    $queryPart = $this->buildSimpleRelationSql($relation, $foreignAlias, $localAlias, $overrideJoin, $join);
-                }
-
-                $queryPart .= $this->buildInheritanceJoinSql($table->getComponentName(), $componentAlias);
-
-                $this->_sqlParts['from'][$componentAlias] = $queryPart;
-                if ( ! empty($joinCondition)) {
-                    $this->_pendingJoinConditions[$componentAlias] = $joinCondition;
-                }
-            }
-            if ($loadFields) {
-
-                $restoreState = false;
-                // load fields if necessary
-                if ($loadFields && empty($this->_dqlParts['select'])) {
-                    $this->_pendingFields[$componentAlias] = array('*');
-                }
-            }
-            $parent = $prevPath;
-        }
-
-        $table = $this->_queryComponents[$componentAlias]['table'];
-
-        return $this->buildIndexBy($componentAlias, $mapWith);
-    }
-
-    protected function buildSimpleRelationSql(IPF_ORM_Relation $relation, $foreignAlias, $localAlias, $overrideJoin, $join)
-    {
-        $queryPart = $join . $this->_conn->quoteIdentifier($relation->getTable()->getTableName())
-                           . ' '
-                           . $this->_conn->quoteIdentifier($foreignAlias);
-
-        if ( ! $overrideJoin) {
-            $queryPart .= ' ON '
-                       . $this->_conn->quoteIdentifier($localAlias . '.' . $relation->getLocal())
-                       . ' = '
-                       . $this->_conn->quoteIdentifier($foreignAlias . '.' . $relation->getForeign());
-        }
-
-        return $queryPart;
-    }
-
-    protected function buildIndexBy($componentAlias, $mapWith = null)
-    {
-        $table = $this->_queryComponents[$componentAlias]['table'];
-
-        $indexBy = null;
-
-        if (isset($mapWith)) {
-            $terms = explode('.', $mapWith);
-
-            if (isset($terms[1])) {
-                $indexBy = $terms[1];
-            }
-        } elseif ($table->getBoundQueryPart('indexBy') !== null) {
-            $indexBy = $table->getBoundQueryPart('indexBy');
-        }
-
-        if ($indexBy !== null) {
-            if ( ! $table->hasColumn($table->getColumnName($indexBy))) {
-                throw new IPF_ORM_Exception("Couldn't use key mapping. Column " . $indexBy . " does not exist.");
-            }
-
-            $this->_queryComponents[$componentAlias]['map'] = $indexBy;
-        }
-
-        return $this->_queryComponents[$componentAlias];
-    }
-
-
-    protected function buildAssociativeRelationSql(IPF_ORM_Relation $relation, $assocAlias, $foreignAlias, $localAlias)
-    {
-        $table = $relation->getTable();
-
-        $queryPart = ' ON ';
-
-        if ($relation->isEqual()) {
-            $queryPart .= '(';
-        }
-
-        $localIdentifier = $table->getColumnName($table->getIdentifier());
-
-        $queryPart .= $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
-                    . ' = '
-                    . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getForeign());
-
-        if ($relation->isEqual()) {
-            $queryPart .= ' OR '
-                        . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
-                        . ' = '
-                        . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getLocal())
-                        . ') AND '
-                        . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
-                        . ' != '
-                        . $this->_conn->quoteIdentifier($localAlias . '.' . $localIdentifier);
-        }
-
-        return $queryPart;
-    }
-
-    public function loadRoot($name, $componentAlias)
-    {
-        // get the connection for the component
-        $manager = IPF_ORM_Manager::getInstance();
-
-        $table = $this->_conn->getTable($name);
-        $tableName = $table->getTableName();
-
-        // get the short alias for this table
-        $tableAlias = $this->getTableAlias($componentAlias, $tableName);
-        // quote table name
-        $queryPart = $this->_conn->quoteIdentifier($tableName);
-
-        if ($this->_type === self::SELECT) {
-            $queryPart .= ' ' . $this->_conn->quoteIdentifier($tableAlias);
-        }
-
-        $this->_tableAliasMap[$tableAlias] = $componentAlias;
-
-        $queryPart .= $this->buildInheritanceJoinSql($name, $componentAlias);
-
-        $this->_sqlParts['from'][] = $queryPart;
-
-        $this->_queryComponents[$componentAlias] = array('table' => $table, 'map' => null);
-
-        return $table;
-    }
-
-    public function buildInheritanceJoinSql($name, $componentAlias)
-    {
-        // get the connection for the component
-        $manager = IPF_ORM_Manager::getInstance();
-
-        $table = $this->_conn->getTable($name);
-        $tableName = $table->getTableName();
-
-        // get the short alias for this table
-        $tableAlias = $this->getTableAlias($componentAlias, $tableName);
-
-        return '';
-    }
-
-    public function getCountQuery()
-    {
-        // triggers dql parsing/processing
-        $this->getQuery(); // this is ugly
-
-        // initialize temporary variables
-        $where  = $this->_sqlParts['where'];
-        $having = $this->_sqlParts['having'];
-        $groupby = $this->_sqlParts['groupby'];
-        $map = reset($this->_queryComponents);
-        $componentAlias = key($this->_queryComponents);
-        $tableAlias = $this->_conn->quoteIdentifier($this->getTableAlias($componentAlias));
-        $table = $map['table'];
-
-        $idColumnNames = array();
-        foreach ($table->getIdentifierColumnNames() as $column) {
-            $idColumnNames[] = $tableAlias . '.' . $this->_conn->quoteIdentifier($column);
-        }
-
-        // build the query base
-        $q  = 'SELECT COUNT(DISTINCT ' . implode(' || ', $idColumnNames) . ') AS num_results';
-
-        foreach ($this->_sqlParts['select'] as $field) {
-            if (strpos($field, '(') !== false) {
-                $q .= ', ' . $field;
-            }
-        }
-
-        $q .= ' FROM ' . $this->_buildSqlFromPart();
-
-        // append column aggregation inheritance (if needed)
-        $string = $this->getInheritanceCondition($this->getRootAlias());
-
-        if ( ! empty($string)) {
-            $where[] = $string;
-        }
-
-        // append conditions
-        $q .= ( ! empty($where)) ?  ' WHERE '  . implode(' AND ', $where) : '';
-
-        if ( ! empty($groupby)) {
-            // Maintain existing groupby
-            $q .= ' GROUP BY '  . implode(', ', $groupby);
-        } else {
-            // Default groupby to primary identifier. Database defaults to this internally
-            // This is required for situations where the user has aggregate functions in the select part
-            // Without the groupby it fails
-            $q .= ' GROUP BY ' . implode(', ', $idColumnNames);
-        }
-
-        $q .= ( ! empty($having)) ? ' HAVING ' . implode(' AND ', $having): '';
-
-        return $q;
-    }
-
-    public function count($params = array())
-    {
-        $q = $this->getCountQuery();
-
-        if ( ! is_array($params)) {
-            $params = array($params);
-        }
-
-        $params = array_merge($this->_params['join'], $this->_params['where'], $this->_params['having'], $params);
-
-        $params = $this->convertEnums($params);
-
-        $results = $this->getConnection()->fetchAll($q, $params);
-
-        if (count($results) > 1) {
-            $count = count($results);
-        } else {
-            if (isset($results[0])) {
-                $results[0] = array_change_key_case($results[0], CASE_LOWER);
-                $count = $results[0]['num_results'];
-            } else {
-                $count = 0;
-            }
-        }
-
-        return (int) $count;
-    }
-
-    public function query($query, $params = array(), $hydrationMode = null)
-    {
-        $this->parseDqlQuery($query);
-        return $this->execute($params, $hydrationMode);
-    }
-
-    public function copy(IPF_ORM_Query $query = null)
-    {
-        if ( ! $query) {
-            $query = $this;
-        }
-
-        $new = clone $query;
-
-        return $new;
-    }
-
-    public function __clone()
-    {
-        $this->_parsers = array();
-    }
-
-    public function free()
-    {
-        $this->reset();
-        $this->_parsers = array();
-        $this->_dqlParts = array();
-        $this->_enumParams = array();
-    }
-
-    public function serialize()
-    {
-        $vars = get_object_vars($this);
-    }
-
-    public function unserialize($serialized)
-    {
-    }
-}
diff --git a/ipf/legacy_orm/orm/query/abstract.php b/ipf/legacy_orm/orm/query/abstract.php
deleted file mode 100644 (file)
index 6e0f48a..0000000
+++ /dev/null
@@ -1,998 +0,0 @@
-<?php
-
-abstract class IPF_ORM_Query_Abstract
-{
-    const SELECT = 0;
-    const DELETE = 1;
-    const UPDATE = 2;
-    const INSERT = 3;
-    const CREATE = 4;
-
-    const STATE_CLEAN  = 1;
-    const STATE_DIRTY  = 2;
-    const STATE_DIRECT = 3;
-    const STATE_LOCKED = 4;
-
-    protected $_tableAliasMap = array();
-    protected $_view;
-    protected $_state = IPF_ORM_Query::STATE_CLEAN;
-    protected $_params = array('join' => array(),
-                               'where' => array(),
-                               'set' => array(),
-                               'having' => array());
-
-    protected $_resultCache;
-    protected $_expireResultCache = false;
-    protected $_resultCacheTTL;
-
-    protected $_queryCache;
-    protected $_expireQueryCache = false;
-    protected $_queryCacheTTL;
-
-    protected $_conn;
-
-    protected $_sqlParts = array(
-            'select'    => array(),
-            'distinct'  => false,
-            'forUpdate' => false,
-            'from'      => array(),
-            'set'       => array(),
-            'join'      => array(),
-            'where'     => array(),
-            'groupby'   => array(),
-            'having'    => array(),
-            'orderby'   => array(),
-            'limit'     => false,
-            'offset'    => false,
-            );
-
-    protected $_dqlParts = array(
-                            'from'      => array(),
-                            'select'    => array(),
-                            'forUpdate' => false,
-                            'set'       => array(),
-                            'join'      => array(),
-                            'where'     => array(),
-                            'groupby'   => array(),
-                            'having'    => array(),
-                            'orderby'   => array(),
-                            'limit'     => array(),
-                            'offset'    => array(),
-                            );
-
-    protected $_queryComponents = array();
-    protected $_type = self::SELECT;
-    protected $_hydrator;
-    protected $_tokenizer;
-    protected $_parser;
-    protected $_tableAliasSeeds = array();
-    protected $_options    = array(
-                            'fetchMode'      => IPF_ORM::FETCH_RECORD
-                            );
-    protected $_enumParams = array();
-
-    protected $_pendingSetParams = array();
-    protected $_components;
-    protected $_preQueried = false;
-
-    public function __construct(IPF_ORM_Connection $connection = null,
-            IPF_ORM_Hydrator_Abstract $hydrator = null)
-    {
-        if ($connection === null) {
-            $connection = IPF_ORM_Manager::connection();
-        }
-        if ($hydrator === null) {
-            $hydrator = new IPF_ORM_Hydrator();
-        }
-        $this->_conn = $connection;
-        $this->_hydrator = $hydrator;
-        $this->_tokenizer = new IPF_ORM_Query_Tokenizer();
-        $this->_resultCacheTTL = $this->_conn->getAttribute(IPF_ORM::ATTR_RESULT_CACHE_LIFESPAN);
-        $this->_queryCacheTTL = $this->_conn->getAttribute(IPF_ORM::ATTR_QUERY_CACHE_LIFESPAN);
-    }
-
-    public function setOption($name, $value)
-    {
-        if ( ! isset($this->_options[$name])) {
-            throw new IPF_ORM_Exception('Unknown option ' . $name);
-        }
-        $this->_options[$name] = $value;
-    }
-
-    public function hasTableAlias($sqlTableAlias)
-    {
-        return $this->hasSqlTableAlias($sqlTableAlias);
-    }
-
-    public function hasSqlTableAlias($sqlTableAlias)
-    {
-        return (isset($this->_tableAliasMap[$sqlTableAlias]));
-    }
-
-    public function getTableAliases()
-    {
-        return $this->getTableAliasMap();
-    }
-
-    public function getTableAliasMap()
-    {
-        return $this->_tableAliasMap;
-    }
-
-    public function getQueryPart($part)
-    {
-        return $this->getSqlQueryPart($part);
-    }
-
-    public function getSqlQueryPart($part)
-    {
-        if ( ! isset($this->_sqlParts[$part])) {
-            throw new IPF_ORM_Exception('Unknown SQL query part ' . $part);
-        }
-        return $this->_sqlParts[$part];
-    }
-
-    public function setQueryPart($name, $part)
-    {
-        return $this->setSqlQueryPart($name, $part);
-    }
-
-    public function setSqlQueryPart($name, $part)
-    {
-        if ( ! isset($this->_sqlParts[$name])) {
-            throw new IPF_ORM_Exception('Unknown query part ' . $name);
-        }
-
-        if ($name !== 'limit' && $name !== 'offset') {
-            if (is_array($part)) {
-                $this->_sqlParts[$name] = $part;
-            } else {
-                $this->_sqlParts[$name] = array($part);
-            }
-        } else {
-            $this->_sqlParts[$name] = $part;
-        }
-
-        return $this;
-    }
-
-    public function addQueryPart($name, $part)
-    {
-        return $this->addSqlQueryPart($name, $part);
-    }
-
-    public function addSqlQueryPart($name, $part)
-    {
-        if ( ! isset($this->_sqlParts[$name])) {
-            throw new IPF_ORM_Exception('Unknown query part ' . $name);
-        }
-        if (is_array($part)) {
-            $this->_sqlParts[$name] = array_merge($this->_sqlParts[$name], $part);
-        } else {
-            $this->_sqlParts[$name][] = $part;
-        }
-        return $this;
-    }
-
-    public function removeQueryPart($name)
-    {
-        return $this->removeSqlQueryPart($name);
-    }
-
-    public function removeSqlQueryPart($name)
-    {
-        if ( ! isset($this->_sqlParts[$name])) {
-            throw new IPF_ORM_Exception('Unknown query part ' . $name);
-        }
-
-        if ($name == 'limit' || $name == 'offset') {
-            $this->_sqlParts[$name] = false;
-        } else {
-            $this->_sqlParts[$name] = array();
-        }
-
-        return $this;
-    }
-
-    public function removeDqlQueryPart($name)
-    {
-        if ( ! isset($this->_dqlParts[$name])) {
-            throw new IPF_ORM_Exception('Unknown query part ' . $name);
-        }
-
-        if ($name == 'limit' || $name == 'offset') {
-            $this->_dqlParts[$name] = false;
-        } else {
-            $this->_dqlParts[$name] = array();
-        }
-
-        return $this;
-    }
-
-    public function getParams($params = array())
-    {
-        return array_merge($this->_params['join'], $this->_params['set'], $this->_params['where'], $this->_params['having'], $params);
-    }
-
-    public function setParams(array $params = array()) {
-        $this->_params = $params;
-    }
-
-    public function setView(IPF_ORM_View $view)
-    {
-        $this->_view = $view;
-    }
-
-    public function getView()
-    {
-        return $this->_view;
-    }
-
-    public function convertEnums($params)
-    {
-        $table = $this->getRoot();
-
-        // $position tracks the position of the parameter, to ensure we're converting
-        // the right parameter value when simple ? placeholders are used.
-        // This only works because SET is only allowed in update statements and it's
-        // the first place where parameters can occur.. see issue #935
-        $position = 0;
-        foreach ($this->_pendingSetParams as $fieldName => $value) {
-            $e = explode('.', $fieldName);
-            $fieldName = isset($e[1]) ? $e[1]:$e[0];
-            if ($table->getTypeOf($fieldName) == 'enum') {
-                $value = $value === '?' ? $position : $value;
-                $this->addEnumParam($value, $table, $fieldName);
-            }
-            ++$position;
-        }
-        $this->_pendingSetParams = array();
-
-        foreach ($this->_enumParams as $key => $values) {
-            if (isset($params[$key])) {
-                if ( ! empty($values)) {
-                    $params[$key] = $values[0]->enumIndex($values[1], $params[$key]);
-                }
-            }
-        }
-
-        return $params;
-    }
-
-    public function getInheritanceCondition($componentAlias)
-    {
-        $map = $this->_queryComponents[$componentAlias]['table']->inheritanceMap;
-
-        // No inheritance map so lets just return
-        if (empty($map)) {
-          return;
-        }
-
-        $tableAlias = $this->getSqlTableAlias($componentAlias);
-
-        if ($this->_type !== IPF_ORM_Query::SELECT) {
-            $tableAlias = '';
-        } else {
-            $tableAlias .= '.';
-        }
-
-        $field = key($map);
-        $value = current($map);
-        $identifier = $this->_conn->quoteIdentifier($tableAlias . $field);
-
-        return $identifier . ' = ' . $this->_conn->quote($value);;
-    }
-
-    public function getTableAlias($componentAlias, $tableName = null)
-    {
-        return $this->getSqlTableAlias($componentAlias, $tableName);
-    }
-
-    public function getSqlTableAlias($componentAlias, $tableName = null)
-    {
-        $alias = array_search($componentAlias, $this->_tableAliasMap);
-
-        if ($alias !== false) {
-            return $alias;
-        }
-
-        if ($tableName === null) {
-            throw new IPF_ORM_Exception("Couldn't get short alias for " . $componentAlias);
-        }
-
-        return $this->generateTableAlias($componentAlias, $tableName);
-    }
-
-    public function generateNewTableAlias($oldAlias)
-    {
-        return $this->generateNewSqlTableAlias($oldAlias);
-    }
-
-    public function generateNewSqlTableAlias($oldAlias)
-    {
-        if (isset($this->_tableAliasMap[$oldAlias])) {
-            // generate a new alias
-            $name = substr($oldAlias, 0, 1);
-            $i    = ((int) substr($oldAlias, 1));
-
-            if ($i == 0) {
-                $i = 1;
-            }
-
-            $newIndex  = ($this->_tableAliasSeeds[$name] + $i);
-
-            return $name . $newIndex;
-        }
-
-        return $oldAlias;
-    }
-
-    public function getTableAliasSeed($sqlTableAlias)
-    {
-        return $this->getSqlTableAliasSeed($sqlTableAlias);
-    }
-
-    public function getSqlTableAliasSeed($sqlTableAlias)
-    {
-        if ( ! isset($this->_tableAliasSeeds[$sqlTableAlias])) {
-            return 0;
-        }
-        return $this->_tableAliasSeeds[$sqlTableAlias];
-    }
-
-    public function hasAliasDeclaration($componentAlias)
-    {
-        return isset($this->_queryComponents[$componentAlias]);
-    }
-
-    public function getAliasDeclaration($componentAlias)
-    {
-        return $this->getQueryComponent($componentAlias);
-    }
-
-    public function getQueryComponent($componentAlias)
-    {
-        if ( ! isset($this->_queryComponents[$componentAlias])) {
-            throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias);
-        }
-
-        return $this->_queryComponents[$componentAlias];
-    }
-
-    public function copyAliases(IPF_ORM_Query_Abstract $query)
-    {
-        $this->_tableAliasMap = $query->_tableAliasMap;
-        $this->_queryComponents     = $query->_queryComponents;
-        $this->_tableAliasSeeds = $query->_tableAliasSeeds;
-        return $this;
-    }
-
-    public function getRootAlias()
-    {
-        if ( ! $this->_queryComponents) {
-          $this->getSql();
-        }
-        reset($this->_queryComponents);
-
-        return key($this->_queryComponents);
-    }
-
-    public function getRootDeclaration()
-    {
-        $map = reset($this->_queryComponents);
-        return $map;
-    }
-
-    public function getRoot()
-    {
-        $map = reset($this->_queryComponents);
-
-        if ( ! isset($map['table'])) {
-            throw new IPF_ORM_Exception('Root component not initialized.');
-        }
-
-        return $map['table'];
-    }
-
-    public function generateTableAlias($componentAlias, $tableName)
-    {
-        return $this->generateSqlTableAlias($componentAlias, $tableName);
-    }
-
-    public function generateSqlTableAlias($componentAlias, $tableName)
-    {
-        preg_match('/([^_])/', $tableName, $matches);
-        $char = strtolower($matches[0]);
-
-        $alias = $char;
-
-        if ( ! isset($this->_tableAliasSeeds[$alias])) {
-            $this->_tableAliasSeeds[$alias] = 1;
-        }
-
-        while (isset($this->_tableAliasMap[$alias])) {
-            if ( ! isset($this->_tableAliasSeeds[$alias])) {
-                $this->_tableAliasSeeds[$alias] = 1;
-            }
-            $alias = $char . ++$this->_tableAliasSeeds[$alias];
-        }
-
-        $this->_tableAliasMap[$alias] = $componentAlias;
-
-        return $alias;
-    }
-
-    public function getComponentAlias($sqlTableAlias)
-    {
-        if ( ! isset($this->_tableAliasMap[$sqlTableAlias])) {
-            throw new IPF_ORM_Exception('Unknown table alias ' . $sqlTableAlias);
-        }
-        return $this->_tableAliasMap[$sqlTableAlias];
-    }
-
-    protected function _execute($params)
-    {
-        $params = $this->_conn->convertBooleans($params);
-
-        if ( ! $this->_view) {
-            if ($this->_queryCache || $this->_conn->getAttribute(IPF_ORM::ATTR_QUERY_CACHE)) {
-                $queryCacheDriver = $this->getQueryCacheDriver();
-                // calculate hash for dql query
-                $dql = $this->getDql();
-                $hash = md5($dql . 'IPF_ORM_QUERY_CACHE_SALT');
-                $cached = $queryCacheDriver->fetch($hash);
-                if ($cached) {
-                    $query = $this->_constructQueryFromCache($cached);
-                } else {
-                    $query = $this->getSqlQuery($params);
-                    $serializedQuery = $this->getCachedForm($query);
-                    $queryCacheDriver->save($hash, $serializedQuery, $this->getQueryCacheLifeSpan());
-                }
-            } else {
-                $query = $this->getSqlQuery($params);
-            }
-            $params = $this->convertEnums($params);
-        } else {
-            $query = $this->_view->getSelectSql();
-        }
-
-        if ($this->_type !== self::SELECT) {
-            return $this->_conn->exec($query, $params);
-        }
-
-        $stmt = $this->_conn->execute($query, $params);
-        return $stmt;
-    }
-
-    public function execute($params = array(), $hydrationMode = null)
-    {
-        $this->_preQuery();
-
-        if ($hydrationMode !== null) {
-            $this->_hydrator->setHydrationMode($hydrationMode);
-        }
-
-        $params = $this->getParams($params);
-
-        if ($this->_resultCache && $this->_type == self::SELECT) {
-            $cacheDriver = $this->getResultCacheDriver();
-
-            $dql = $this->getDql();
-            // calculate hash for dql query
-            $hash = md5($dql . var_export($params, true));
-
-            $cached = ($this->_expireResultCache) ? false : $cacheDriver->fetch($hash);
-
-            if ($cached === false) {
-                // cache miss
-                $stmt = $this->_execute($params);
-                $this->_hydrator->setQueryComponents($this->_queryComponents);
-                $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap);
-
-                $cached = $this->getCachedForm($result);
-                $cacheDriver->save($hash, $cached, $this->getResultCacheLifeSpan());
-            } else {
-                $result = $this->_constructQueryFromCache($cached);
-            }
-        } else {
-            $stmt = $this->_execute($params);
-
-            if (is_integer($stmt)) {
-                $result = $stmt;
-            } else {
-                $this->_hydrator->setQueryComponents($this->_queryComponents);
-                $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap);
-            }
-        }
-
-        return $result;
-    }
-
-    protected function _preQuery()
-    {
-        // Invoke preQuery() hook on IPF_ORM_Query for child classes which implement this hook
-        $this->preQuery();
-    }
-
-    public function preQuery()
-    {
-    }
-
-    protected function _constructQueryFromCache($cached)
-    {
-        $cached = unserialize($cached);
-        $this->_tableAliasMap = $cached[2];
-        $customComponent = $cached[0];
-
-        $queryComponents = array();
-        $cachedComponents = $cached[1];
-        foreach ($cachedComponents as $alias => $components) {
-            $e = explode('.', $components[0]);
-            if (count($e) === 1) {
-                $queryComponents[$alias]['table'] = $this->_conn->getTable($e[0]);
-            } else {
-                $queryComponents[$alias]['parent'] = $e[0];
-                $queryComponents[$alias]['relation'] = $queryComponents[$e[0]]['table']->getRelation($e[1]);
-                $queryComponents[$alias]['table'] = $queryComponents[$alias]['relation']->getTable();
-            }
-            if (isset($components[1])) {
-                $queryComponents[$alias]['agg'] = $components[1];
-            }
-            if (isset($components[2])) {
-                $queryComponents[$alias]['map'] = $components[2];
-            }
-        }
-        $this->_queryComponents = $queryComponents;
-
-        return $customComponent;
-    }
-
-    public function getCachedForm($customComponent = null)
-    {
-        $componentInfo = array();
-
-        foreach ($this->getQueryComponents() as $alias => $components) {
-            if ( ! isset($components['parent'])) {
-                $componentInfo[$alias][] = $components['table']->getComponentName();
-            } else {
-                $componentInfo[$alias][] = $components['parent'] . '.' . $components['relation']->getAlias();
-            }
-            if (isset($components['agg'])) {
-                $componentInfo[$alias][] = $components['agg'];
-            }
-            if (isset($components['map'])) {
-                $componentInfo[$alias][] = $components['map'];
-            }
-        }
-
-        return serialize(array($customComponent, $componentInfo, $this->getTableAliasMap()));
-    }
-
-    public function addSelect($select)
-    {
-        return $this->_addDqlQueryPart('select', $select, true);
-    }
-
-    public function addTableAlias($tableAlias, $componentAlias)
-    {
-        return $this->addSqlTableAlias($tableAlias, $componentAlias);
-    }
-
-    public function addSqlTableAlias($sqlTableAlias, $componentAlias)
-    {
-        $this->_tableAliasMap[$sqlTableAlias] = $componentAlias;
-        return $this;
-    }
-
-    public function addFrom($from)
-    {
-        return $this->_addDqlQueryPart('from', $from, true);
-    }
-
-    public function addWhere($where, $params = array())
-    {
-        if (is_array($params)) {
-            $this->_params['where'] = array_merge($this->_params['where'], $params);
-        } else {
-            $this->_params['where'][] = $params;
-        }
-        return $this->_addDqlQueryPart('where', $where, true);
-    }
-
-    public function whereIn($expr, $params = array(), $not = false)
-    {
-        $params = (array) $params;
-
-        // if there's no params, return (else we'll get a WHERE IN (), invalid SQL)
-        if (!count($params))
-          return $this;
-
-        $a = array();
-        foreach ($params as $k => $value) {
-            if ($value instanceof IPF_ORM_Expression) {
-                $value = $value->getSql();
-                unset($params[$k]);
-            } else {
-                $value = '?';
-            }
-            $a[] = $value;
-        }
-
-        $this->_params['where'] = array_merge($this->_params['where'], $params);
-
-        $where = $expr . ($not === true ? ' NOT ':'') . ' IN (' . implode(', ', $a) . ')';
-
-        return $this->_addDqlQueryPart('where', $where, true);
-    }
-
-    public function whereNotIn($expr, $params = array())
-    {
-        return $this->whereIn($expr, $params, true);
-    }
-
-    public function addGroupBy($groupby)
-    {
-        return $this->_addDqlQueryPart('groupby', $groupby, true);
-    }
-
-    public function addHaving($having, $params = array())
-    {
-        if (is_array($params)) {
-            $this->_params['having'] = array_merge($this->_params['having'], $params);
-        } else {
-            $this->_params['having'][] = $params;
-        }
-        return $this->_addDqlQueryPart('having', $having, true);
-    }
-
-    public function addOrderBy($orderby)
-    {
-        return $this->_addDqlQueryPart('orderby', $orderby, true);
-    }
-
-    public function select($select)
-    {
-        return $this->_addDqlQueryPart('select', $select);
-    }
-
-    public function distinct($flag = true)
-    {
-        $this->_sqlParts['distinct'] = (bool) $flag;
-        return $this;
-    }
-
-    public function forUpdate($flag = true)
-    {
-        $this->_sqlParts['forUpdate'] = (bool) $flag;
-        return $this;
-    }
-
-    public function delete()
-    {
-        $this->_type = self::DELETE;
-        return $this;
-    }
-
-    public function update($update)
-    {
-        $this->_type = self::UPDATE;
-        return $this->_addDqlQueryPart('from', $update);
-    }
-
-    public function set($key, $value, $params = null)
-    {
-        if (is_array($key)) {
-            foreach ($key as $k => $v) {
-                $this->set($k, '?', array($v));
-            }
-            return $this;
-        } else {
-            if ($params !== null) {
-                if (is_array($params)) {
-                    $this->_params['set'] = array_merge($this->_params['set'], $params);
-                } else {
-                    $this->_params['set'][] = $params;
-                }
-            }
-
-            $this->_pendingSetParams[$key] = $value;
-
-            return $this->_addDqlQueryPart('set', $key . ' = ' . $value, true);
-        }
-    }
-
-    public function from($from)
-    {
-        return $this->_addDqlQueryPart('from', $from);
-    }
-
-    public function innerJoin($join, $params = array())
-    {
-        if (is_array($params)) {
-            $this->_params['join'] = array_merge($this->_params['join'], $params);
-        } else {
-            $this->_params['join'][] = $params;
-        }
-
-        return $this->_addDqlQueryPart('from', 'INNER JOIN ' . $join, true);
-    }
-
-    public function leftJoin($join, $params = array())
-    {
-        if (is_array($params)) {
-            $this->_params['join'] = array_merge($this->_params['join'], $params);
-        } else {
-            $this->_params['join'][] = $params;
-        }
-
-        return $this->_addDqlQueryPart('from', 'LEFT JOIN ' . $join, true);
-    }
-
-    public function groupBy($groupby)
-    {
-        return $this->_addDqlQueryPart('groupby', $groupby);
-    }
-
-    public function where($where, $params = array())
-    {
-        $this->_params['where'] = array();
-        if (is_array($params)) {
-            $this->_params['where'] = $params;
-        } else {
-            $this->_params['where'][] = $params;
-        }
-
-        return $this->_addDqlQueryPart('where', $where);
-    }
-
-    public function having($having, $params = array())
-    {
-        $this->_params['having'] = array();
-        if (is_array($params)) {
-            $this->_params['having'] = $params;
-        } else {
-            $this->_params['having'][] = $params;
-        }
-
-        return $this->_addDqlQueryPart('having', $having);
-    }
-
-    public function orderBy($orderby)
-    {
-        return $this->_addDqlQueryPart('orderby', $orderby);
-    }
-
-    public function limit($limit)
-    {
-        return $this->_addDqlQueryPart('limit', $limit);
-    }
-
-    public function offset($offset)
-    {
-        return $this->_addDqlQueryPart('offset', $offset);
-    }
-
-    public function getSql($params = array())
-    {
-        return $this->getSqlQuery($params);
-    }
-
-    protected function clear()
-    {
-        $this->_sqlParts = array(
-                    'select'    => array(),
-                    'distinct'  => false,
-                    'forUpdate' => false,
-                    'from'      => array(),
-                    'set'       => array(),
-                    'join'      => array(),
-                    'where'     => array(),
-                    'groupby'   => array(),
-                    'having'    => array(),
-                    'orderby'   => array(),
-                    'limit'     => false,
-                    'offset'    => false,
-                    );
-    }
-
-    public function setHydrationMode($hydrationMode)
-    {
-        $this->_hydrator->setHydrationMode($hydrationMode);
-        return $this;
-    }
-
-    public function getAliasMap()
-    {
-        return $this->_queryComponents;
-    }
-
-    public function getQueryComponents()
-    {
-        return $this->_queryComponents;
-    }
-
-    public function getParts()
-    {
-        return $this->getSqlParts();
-    }
-
-    public function getSqlParts()
-    {
-        return $this->_sqlParts;
-    }
-
-    public function getType()
-    {
-        return $this->_type;
-    }
-
-    public function useCache($driver = true, $timeToLive = null)
-    {
-        return $this->useResultCache($driver, $timeToLive);
-    }
-
-    public function useResultCache($driver = true, $timeToLive = null)
-    {
-        if ($driver !== null && $driver !== true && ! ($driver instanceOf IPF_ORM_Cache_Interface)) {
-            $msg = 'First argument should be instance of IPF_ORM_Cache_Interface or null.';
-            throw new IPF_ORM_Exception($msg);
-        }
-        $this->_resultCache = $driver;
-
-        return $this->setResultCacheLifeSpan($timeToLive);
-    }
-
-    public function useQueryCache(IPF_ORM_Cache_Interface $driver, $timeToLive = null)
-    {
-        $this->_queryCache = $driver;
-        return $this->setQueryCacheLifeSpan($timeToLive);
-    }
-
-    public function expireCache($expire = true)
-    {
-        return $this->expireResultCache($expire);
-    }
-
-    public function expireResultCache($expire = true)
-    {
-        $this->_expireResultCache = true;
-        return $this;
-    }
-
-    public function expireQueryCache($expire = true)
-    {
-        $this->_expireQueryCache = true;
-        return $this;
-    }
-
-    public function setCacheLifeSpan($timeToLive)
-    {
-        return $this->setResultCacheLifeSpan($timeToLive);
-    }
-
-    public function setResultCacheLifeSpan($timeToLive)
-    {
-        if ($timeToLive !== null) {
-            $timeToLive = (int) $timeToLive;
-        }
-        $this->_resultCacheTTL = $timeToLive;
-
-        return $this;
-    }
-
-    public function getResultCacheLifeSpan()
-    {
-        return $this->_resultCacheTTL;
-    }
-
-    public function setQueryCacheLifeSpan($timeToLive)
-    {
-        if ($timeToLive !== null) {
-            $timeToLive = (int) $timeToLive;
-        }
-        $this->_queryCacheTTL = $timeToLive;
-
-        return $this;
-    }
-
-    public function getQueryCacheLifeSpan()
-    {
-        return $this->_queryCacheTTL;
-    }
-
-    public function getCacheDriver()
-    {
-        return $this->getResultCacheDriver();
-    }
-
-    public function getResultCacheDriver()
-    {
-        if ($this->_resultCache instanceof IPF_ORM_Cache_Interface) {
-            return $this->_resultCache;
-        } else {
-            return $this->_conn->getResultCacheDriver();
-        }
-    }
-
-    public function getQueryCacheDriver()
-    {
-        if ($this->_queryCache instanceof IPF_ORM_Cache_Interface) {
-            return $this->_queryCache;
-        } else {
-            return $this->_conn->getQueryCacheDriver();
-        }
-    }
-
-    public function getConnection()
-    {
-        return $this->_conn;
-    }
-
-    protected function _addDqlQueryPart($queryPartName, $queryPart, $append = false)
-    {
-        if ($append) {
-            $this->_dqlParts[$queryPartName][] = $queryPart;
-        } else {
-            $this->_dqlParts[$queryPartName] = array($queryPart);
-        }
-
-        $this->_state = IPF_ORM_Query::STATE_DIRTY;
-        return $this;
-    }
-
-    protected function _processDqlQueryPart($queryPartName, $queryParts)
-    {
-        $this->removeSqlQueryPart($queryPartName);
-
-        if (is_array($queryParts) && ! empty($queryParts)) {
-            foreach ($queryParts as $queryPart) {
-                $parser = $this->_getParser($queryPartName);
-                $sql = $parser->parse($queryPart);
-                if (isset($sql)) {
-                    if ($queryPartName == 'limit' || $queryPartName == 'offset') {
-                        $this->setSqlQueryPart($queryPartName, $sql);
-                    } else {
-                        $this->addSqlQueryPart($queryPartName, $sql);
-                    }
-                }
-            }
-        }
-    }
-
-    protected function _getParser($name)
-    {
-        if ( ! isset($this->_parsers[$name])) {
-            $class = 'IPF_ORM_Query_' . ucwords(strtolower($name));
-
-            //IPF_ORM::autoload($class);
-
-            if ( ! class_exists($class)) {
-                throw new IPF_ORM_Exception('Unknown parser ' . $name);
-            }
-
-            $this->_parsers[$name] = new $class($this, $this->_tokenizer);
-        }
-
-        return $this->_parsers[$name];
-    }
-
-    abstract public function getSqlQuery($params = array());
-
-    abstract public function parseDqlQuery($query);
-
-    public function parseQuery($query)
-    {
-        return $this->parseDqlQuery($query);
-    }
-
-    public function getQuery($params = array())
-    {
-        return $this->getSqlQuery($params);
-    }
-
-    public function __debugInfo()
-    {
-        return array('sql' => $this->getQuery());
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/query/check.php b/ipf/legacy_orm/orm/query/check.php
deleted file mode 100644 (file)
index 79728c3..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Check
-{
-    protected $table;
-
-    protected $sql;
-    
-    protected $_tokenizer;
-
-    public function __construct($table)
-    {
-        if ( ! ($table instanceof IPF_ORM_Table)) {
-            $table = IPF_ORM_Manager::connection()->getTable($table);
-        }
-        $this->table = $table;
-        $this->_tokenizer = new IPF_ORM_Query_Tokenizer();
-    }
-
-    public function getTable()
-    {
-        return $this->table;
-    }
-
-    public function parse($dql)
-    {
-        $this->sql = $this->parseClause($dql);
-    }
-
-    public function parseClause($dql)
-    {
-        $parts = $this->_tokenizer->sqlExplode($dql, ' AND ');
-
-        if (count($parts) > 1) {
-            $ret = array();
-            foreach ($parts as $part) {
-                $ret[] = $this->parseSingle($part);
-            }
-
-            $r = implode(' AND ', $ret);
-        } else {
-            $parts = $this->_tokenizer->quoteExplode($dql, ' OR ');
-            if (count($parts) > 1) {
-                $ret = array();
-                foreach ($parts as $part) {
-                    $ret[] = $this->parseClause($part);
-                }
-
-                $r = implode(' OR ', $ret);
-            } else {
-                $ret = $this->parseSingle($dql);
-                return $ret;
-            }
-        }
-        return '(' . $r . ')';
-    }
-    
-    public function parseSingle($part)
-    {
-        $e = explode(' ', $part);
-        
-        $e[0] = $this->parseFunction($e[0]);
-
-        switch ($e[1]) {
-            case '>':
-            case '<':
-            case '=':
-            case '!=':
-            case '<>':
-
-            break;
-            default:
-                throw new IPF_ORM_Exception('Unknown operator ' . $e[1]);
-        }
-
-        return implode(' ', $e);
-    }
-    public function parseFunction($dql) 
-    {
-        if (($pos = strpos($dql, '(')) !== false) {
-            $func  = substr($dql, 0, $pos);
-            $value = substr($dql, ($pos + 1), -1);
-            
-            $expr  = $this->table->getConnection()->expression;
-
-            if ( ! method_exists($expr, $func)) {
-                throw new IPF_ORM_Exception('Unknown function ' . $func);
-            }
-            
-            $func  = $expr->$func($value);
-        }
-        return $func;
-    }
-
-    public function getSql()
-    {
-        return $this->sql;
-    }
-}
diff --git a/ipf/legacy_orm/orm/query/condition.php b/ipf/legacy_orm/orm/query/condition.php
deleted file mode 100644 (file)
index b18e9eb..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-
-abstract class IPF_ORM_Query_Condition extends IPF_ORM_Query_Part
-{
-    public function parse($str)
-    {
-        $tmp = trim($str);
-
-        $parts = $this->_tokenizer->bracketExplode($str, array(' \&\& ', ' AND '), '(', ')');
-
-        if (count($parts) > 1) {
-            $ret = array();
-            foreach ($parts as $part) {
-                $part = $this->_tokenizer->bracketTrim($part, '(', ')');
-                $ret[] = $this->parse($part);
-            }
-            $r = implode(' AND ', $ret);
-        } else {
-
-            $parts = $this->_tokenizer->bracketExplode($str, array(' \|\| ', ' OR '), '(', ')');
-            if (count($parts) > 1) {
-                $ret = array();
-                foreach ($parts as $part) {
-                    $part = $this->_tokenizer->bracketTrim($part, '(', ')');
-                    $ret[] = $this->parse($part);
-                }
-                $r = implode(' OR ', $ret);
-            } else {
-                // Fix for #710
-                if (substr($parts[0],0,1) == '(' && substr($parts[0], -1) == ')') {
-                    return $this->parse(substr($parts[0], 1, -1));
-                } else {
-                    // Processing NOT here
-                    if (strtoupper(substr($parts[0], 0, 4)) === 'NOT ') {
-                        $r = 'NOT ('.$this->parse(substr($parts[0], 4)).')';
-                    } else {
-                        return $this->load($parts[0]);
-                    }
-                }
-            }
-        }
-
-        return '(' . $r . ')';
-    }
-
-    private function parseBoolean($value)
-    {
-        // parse booleans
-        if ($value == 'true') {
-            $value = 1;
-        } elseif ($value == 'false') {
-            $value = 0;
-        }
-        return $value;
-    }
-
-    public function parseLiteralValue($value)
-    {
-        // check that value isn't a string
-        if (strpos($value, '\'') === false) {
-            // parse booleans
-            $value = $this->parseBoolean($value);
-
-            $a = explode('.', $value);
-
-            if (count($a) > 1) {
-            // either a float or a component..
-
-                if ( ! is_numeric($a[0])) {
-                    // a component found
-                    $field     = array_pop($a);
-                         $reference = implode('.', $a);
-                    $value = $this->query->getTableAlias($reference). '.' . $field;
-                }
-            }
-        } else {
-            // string literal found
-        }
-        return $value;
-    }
-}
diff --git a/ipf/legacy_orm/orm/query/filter.php b/ipf/legacy_orm/orm/query/filter.php
deleted file mode 100644 (file)
index 65b0917..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Filter implements IPF_ORM_Query_Filter_Interface
-{
-    public function preQuery(IPF_ORM_Query $query)
-    {
-    }
-
-    public function postQuery(IPF_ORM_Query $query)
-    {
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/query/filter/chain.php b/ipf/legacy_orm/orm/query/filter/chain.php
deleted file mode 100644 (file)
index 5444395..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Filter_Chain
-{
-    protected $_filters = array();
-
-    public function add(IPF_ORM_Query_Filter $filter)
-    {
-        $this->_filters[] = $filter;
-    }
-
-    public function get($key)
-    {
-        if ( ! isset($this->_filters[$key])) {
-            throw new IPF_ORM_Exception('Unknown filter ' . $key);
-        }
-        return $this->_filters[$key];
-    }
-
-    public function set($key, IPF_ORM_Query_Filter $listener)
-    {
-        $this->_filters[$key] = $listener;
-    }
-
-    public function preQuery(IPF_ORM_Query $query)
-    {
-        foreach ($this->_filters as $filter) {
-            $filter->preQuery($query);
-        }
-    }
-
-    public function postQuery(IPF_ORM_Query $query)
-    {
-        foreach ($this->_filters as $filter) {
-            $filter->postQuery($query);
-        }
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/query/filter/interface.php b/ipf/legacy_orm/orm/query/filter/interface.php
deleted file mode 100644 (file)
index 09c104f..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?php
-
-interface IPF_ORM_Query_Filter_Interface
-{
-    public function preQuery(IPF_ORM_Query $query);
-    public function postQuery(IPF_ORM_Query $query);
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/query/from.php b/ipf/legacy_orm/orm/query/from.php
deleted file mode 100644 (file)
index d2c2668..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-
-class IPF_ORM_Query_From extends IPF_ORM_Query_Part
-{
-    public function parse($str, $return = false)
-    {
-        $str = trim($str);
-        $parts = $this->_tokenizer->bracketExplode($str, 'JOIN');
-
-        $from = $return ? array() : null;
-
-        $operator = false;
-
-        switch (trim($parts[0])) {
-            case 'INNER':
-                $operator = ':';
-            case 'LEFT':
-                array_shift($parts);
-            break;
-        }
-
-        $last = '';
-
-        foreach ($parts as $k => $part) {
-            $part = trim($part);
-
-            if (empty($part)) {
-                continue;
-            }
-
-            $e = explode(' ', $part);
-
-            if (end($e) == 'INNER' || end($e) == 'LEFT') {
-                $last = array_pop($e);
-            }
-            $part = implode(' ', $e);
-
-            foreach ($this->_tokenizer->bracketExplode($part, ',') as $reference) {
-                $reference = trim($reference);
-                $e = explode(' ', $reference);
-                $e2 = explode('.', $e[0]);
-
-                if ($operator) {
-                    $e[0] = array_shift($e2) . $operator . implode('.', $e2);
-                }
-
-                if ($return) {
-                    $from[] = $e;
-                } else {
-                    $table = $this->query->load(implode(' ', $e));
-                }
-            }
-
-            $operator = ($last == 'INNER') ? ':' : '.';
-        }
-        return $from;
-    }
-}
diff --git a/ipf/legacy_orm/orm/query/groupby.php b/ipf/legacy_orm/orm/query/groupby.php
deleted file mode 100644 (file)
index 824f11c..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Groupby extends IPF_ORM_Query_Part
-{
-    public function parse($str, $append = false)
-    {
-        $r = array();
-        foreach (explode(',', $str) as $reference) {
-            $reference = trim($reference);
-
-            $r[] = $this->query->parseClause($reference);
-        }
-        return implode(', ', $r);
-    }
-}
diff --git a/ipf/legacy_orm/orm/query/having.php b/ipf/legacy_orm/orm/query/having.php
deleted file mode 100644 (file)
index 3fe3b6d..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Having extends IPF_ORM_Query_Condition
-{
-    private function parseAggregateFunction($func)
-    {
-        $pos = strpos($func, '(');
-
-        if ($pos !== false) {
-            $funcs  = array();
-
-            $name   = substr($func, 0, $pos);
-            $func   = substr($func, ($pos + 1), -1);
-            $params = $this->_tokenizer->bracketExplode($func, ',', '(', ')');
-
-            foreach ($params as $k => $param) {
-                $params[$k] = $this->parseAggregateFunction($param);
-            }
-
-            $funcs = $name . '(' . implode(', ', $params) . ')';
-
-            return $funcs;
-
-        } else {
-            if ( ! is_numeric($func)) {
-                $a = explode('.', $func);
-
-                if (count($a) > 1) {
-                    $field     = array_pop($a);
-                    $reference = implode('.', $a);
-                    $map       = $this->query->load($reference, false);
-                    $field     = $map['table']->getColumnName($field);
-                    $func      = $this->query->getTableAlias($reference) . '.' . $field;
-                } else {
-                    $field = end($a);
-                    $func  = $this->query->getAggregateAlias($field);
-                }
-                return $func;
-            } else {
-                return $func;
-            }
-        }
-    }
-
-    final public function load($having)
-    {
-        $tokens = $this->_tokenizer->bracketExplode($having, ' ', '(', ')');
-        $part = $this->parseAggregateFunction(array_shift($tokens));
-        $operator  = array_shift($tokens);
-        $value     = implode(' ', $tokens);
-        $part .= ' ' . $operator . ' ' . $value;
-        // check the RHS for aggregate functions
-        if (strpos($value, '(') !== false) {
-          $value = $this->parseAggregateFunction($value);
-        }
-        return $part;
-    }
-}
diff --git a/ipf/legacy_orm/orm/query/joincondition.php b/ipf/legacy_orm/orm/query/joincondition.php
deleted file mode 100644 (file)
index 94d969b..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-<?php
-
-class IPF_ORM_Query_JoinCondition extends IPF_ORM_Query_Condition 
-{
-    public function load($condition) 
-    {
-        $condition = trim($condition);
-
-        $e = $this->_tokenizer->sqlExplode($condition);
-
-        if (count($e) > 2) {
-            $expr = new IPF_ORM_Expression($e[0], $this->query->getConnection());
-            $e[0] = $expr->getSql();
-
-            $operator  = $e[1];
-
-            if (substr(trim($e[2]), 0, 1) != '(') {
-                $expr = new IPF_ORM_Expression($e[2], $this->query->getConnection());
-                $e[2] = $expr->getSql();
-            }
-
-            // We need to check for agg functions here
-            $hasLeftAggExpression = preg_match('/(.*)\(([^\)]*)\)([\)]*)/', $e[0], $leftMatches);
-
-            if ($hasLeftAggExpression) {
-                $e[0] = $leftMatches[2];
-            }
-
-            $hasRightAggExpression = preg_match('/(.*)\(([^\)]*)\)([\)]*)/', $e[2], $rightMatches);
-
-            if ($hasRightAggExpression) {
-                $e[2] = $rightMatches[2];
-            }
-
-            $a         = explode('.', $e[0]);
-            $field     = array_pop($a);
-            $reference = implode('.', $a);
-            $value     = $e[2];
-
-            $conn      = $this->query->getConnection();
-            $alias     = $this->query->getTableAlias($reference);
-            $map       = $this->query->getAliasDeclaration($reference);
-            $table     = $map['table'];
-            // check if value is enumerated value
-            $enumIndex = $table->enumIndex($field, trim($value, "'"));
-
-            if (false !== $enumIndex && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
-                $enumIndex = $conn->quote($enumIndex, 'text');
-            }
-
-            // FIX: Issues with "(" XXX ")"
-            if ($hasRightAggExpression) {
-                $value = '(' . $value . ')';
-            }
-
-            if (substr($value, 0, 1) == '(') {
-                // trim brackets
-                $trimmed   = $this->_tokenizer->bracketTrim($value);
-
-                if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') {
-                    // subquery found
-                    $q = $this->query->createSubquery();
-
-                    // Change due to bug "(" XXX ")"
-                    //$value = '(' . $q->parseQuery($trimmed)->getQuery() . ')';
-                    $value = $q->parseQuery($trimmed)->getQuery();
-                } elseif (substr($trimmed, 0, 4) == 'SQL:') {
-                    // Change due to bug "(" XXX ")"
-                    //$value = '(' . substr($trimmed, 4) . ')';
-                    $value = substr($trimmed, 4);
-                } else {
-                    // simple in expression found
-                    $e     = $this->_tokenizer->sqlExplode($trimmed, ',');
-
-                    $value = array();
-                    foreach ($e as $part) {
-                        $index = $table->enumIndex($field, trim($part, "'"));
-
-                        if (false !== $index && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
-                            $index = $conn->quote($index, 'text');
-                        }
-
-                        if ($index !== false) {
-                            $value[] = $index;
-                        } else {
-                            $value[] = $this->parseLiteralValue($part);
-                        }
-                    }
-
-                    // Change due to bug "(" XXX ")"
-                    //$value = '(' . implode(', ', $value) . ')';
-                    $value = implode(', ', $value);
-                }
-            } else {
-                if ($enumIndex !== false) {
-                    $value = $enumIndex;
-                } else {
-                    $value = $this->parseLiteralValue($value);
-                }
-            }
-
-            switch ($operator) {
-                case '<':
-                case '>':
-                case '=':
-                case '!=':
-                    if ($enumIndex !== false) {
-                        $value  = $enumIndex;
-                    }
-                default:
-                    $leftExpr = (($hasLeftAggExpression) ? $leftMatches[1] . '(' : '') 
-                              . $alias . '.' . $field
-                              . (($hasLeftAggExpression) ? $leftMatches[3] . ')' : '') ;
-
-                    $rightExpr = (($hasRightAggExpression) ? $rightMatches[1] . '(' : '') 
-                              . $value
-                              . (($hasRightAggExpression) ? $rightMatches[3] . ')' : '') ;
-
-                    $condition  = $leftExpr . ' ' . $operator . ' ' . $rightExpr;
-            }
-
-        }
-
-        return $condition;
-    }
-}
diff --git a/ipf/legacy_orm/orm/query/limit.php b/ipf/legacy_orm/orm/query/limit.php
deleted file mode 100644 (file)
index f0cd5ae..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Limit extends IPF_ORM_Query_Part
-{
-    public function parse($limit) 
-    {
-        return (int) $limit;
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/query/offset.php b/ipf/legacy_orm/orm/query/offset.php
deleted file mode 100644 (file)
index 9f32a62..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Offset extends IPF_ORM_Query_Part
-{
-    public function parse($offset)
-    {
-        return (int) $offset;
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/query/orderby.php b/ipf/legacy_orm/orm/query/orderby.php
deleted file mode 100644 (file)
index 5b04cda..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Orderby extends IPF_ORM_Query_Part
-{
-    public function parse($str, $append = false)
-    {
-        $ret = array();
-
-        foreach (explode(',', trim($str)) as $r) {
-            $r = $this->query->parseClause($r);
-
-            $ret[] = $r;
-        }
-        return $ret;
-    }
-}
diff --git a/ipf/legacy_orm/orm/query/parser.php b/ipf/legacy_orm/orm/query/parser.php
deleted file mode 100644 (file)
index 71d6f1b..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Parser 
-{
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/query/part.php b/ipf/legacy_orm/orm/query/part.php
deleted file mode 100644 (file)
index b9975bf..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-abstract class IPF_ORM_Query_Part
-{
-    protected $query;
-    
-    protected $_tokenizer;
-
-    public function __construct($query, IPF_ORM_Query_Tokenizer $tokenizer = null)
-    {
-        $this->query = $query;
-        if ( ! $tokenizer) {
-            $tokenizer = new IPF_ORM_Query_Tokenizer();
-        }
-        $this->_tokenizer = $tokenizer;
-    }
-
-    public function getQuery()
-    {
-        return $this->query;
-    }
-}
diff --git a/ipf/legacy_orm/orm/query/registry.php b/ipf/legacy_orm/orm/query/registry.php
deleted file mode 100644 (file)
index f35ff42..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Registry
-{
-    protected $_queries = array();
-
-    public function add($key, $query)
-    {
-       if (strpos($key, '/') === false) {
-            $this->_queries[$key] = $query;
-        } else {
-            // namespace found
-            $e = explode('/', $key);
-
-            $this->_queries[$e[0]][$e[1]] = $query;
-        }
-    }
-    
-    public function get($key, $namespace = null)
-    {
-        if (isset($namespace)) {
-            if ( ! isset($this->_queries[$namespace][$key])) {
-                throw new IPF_ORM_Exception('A query with the name ' . $namespace . '/' . $key . ' does not exist.');
-            }
-            $query = $this->_queries[$namespace][$key];
-        } else {
-            if ( ! isset($this->_queries[$key])) {
-                throw new IPF_ORM_Exception('A query with the name ' . $key . ' does not exist.');
-            }
-            $query = $this->_queries[$key];
-        }
-        
-        if ( ! ($query instanceof IPF_ORM_Query)) {
-            $query = IPF_ORM_Query::create()->parseQuery($query);
-        }
-        
-        return $query;
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/query/select.php b/ipf/legacy_orm/orm/query/select.php
deleted file mode 100644 (file)
index 0994a68..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Select extends IPF_ORM_Query_Part
-{
-    public function parse($dql) 
-    {
-        $this->query->parseSelect($dql);
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/query/set.php b/ipf/legacy_orm/orm/query/set.php
deleted file mode 100644 (file)
index 5e26e3b..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Set extends IPF_ORM_Query_Part
-{
-    public function parse($dql)
-    {
-       $terms = $this->_tokenizer->sqlExplode($dql, ' ');
-       foreach ($terms as $term) {
-            preg_match_all("/[a-z0-9_]+\.[a-z0-9_]+[\.[a-z0-9]+]*/i", $term, $m);
-            
-            if (isset($m[0])) {
-                foreach ($m[0] as $part) {
-                    $e = explode('.', trim($part));
-                    $field = array_pop($e);
-        
-                    $reference = implode('.', $e);
-        
-                    $alias = $this->query->getTableAlias($reference);
-                    $map   = $this->query->getAliasDeclaration($reference);
-        
-                    $dql = str_replace($part, $map['table']->getColumnName($field), $dql);
-                }
-            }
-        }
-        return $dql;
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/query/tokenizer.php b/ipf/legacy_orm/orm/query/tokenizer.php
deleted file mode 100644 (file)
index f9a7d13..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Tokenizer
-{
-    public function tokenizeQuery($query)
-    {
-        $parts = array();
-        $tokens = $this->sqlExplode($query, ' ');
-
-        foreach ($tokens as $index => $token) {
-            $token = trim($token);
-            switch (strtolower($token)) {
-                case 'delete':
-                case 'update':
-                case 'select':
-                case 'set':
-                case 'from':
-                case 'where':
-                case 'limit':
-                case 'offset':
-                case 'having':
-                    $p = $token;
-                    //$parts[$token] = array();
-                    $parts[$token] = '';
-                break;
-                case 'order':
-                case 'group':
-                    $i = ($index + 1);
-                    if (isset($tokens[$i]) && strtolower($tokens[$i]) === 'by') {
-                        $p = $token;
-                        $parts[$token] = '';
-                        //$parts[$token] = array();
-                    } else {
-                        $parts[$p] .= "$token ";
-                        //$parts[$p][] = $token;
-                    }
-                break;
-                case 'by':
-                    continue;
-                default:
-                    if ( ! isset($p)) {
-                        throw new IPF_ORM_Exception(
-                                "Couldn't tokenize query. Encountered invalid token: '$token'.");
-                    }
-
-                    $parts[$p] .= "$token ";
-                    //$parts[$p][] = $token;
-            }
-        }
-        return $parts;
-    }
-
-    public function bracketTrim($str, $e1 = '(', $e2 = ')')
-    {
-        if (substr($str, 0, 1) === $e1 && substr($str, -1) === $e2) {
-            return substr($str, 1, -1);
-        } else {
-            return $str;
-        }
-    }
-
-    public function bracketExplode($str, $d = ' ', $e1 = '(', $e2 = ')')
-    {
-        if (is_array($d)) {
-            $a = preg_split('#('.implode('|', $d).')#i', $str);
-            $d = stripslashes($d[0]);
-        } else {
-            $a = explode($d, $str);
-        }
-
-        $i = 0;
-        $term = array();
-        foreach($a as $key=>$val) {
-            if (empty($term[$i])) {
-                $term[$i] = trim($val);
-                $s1 = substr_count($term[$i], $e1);
-                $s2 = substr_count($term[$i], $e2);
-
-                if ($s1 == $s2) {
-                    $i++;
-                }
-            } else {
-                $term[$i] .= $d . trim($val);
-                $c1 = substr_count($term[$i], $e1);
-                $c2 = substr_count($term[$i], $e2);
-
-                if ($c1 == $c2) {
-                    $i++;
-                }
-            }
-        }
-        return $term;
-    }
-
-    public function quoteExplode($str, $d = ' ')
-    {
-        if (is_array($d)) {
-            $a = preg_split('/('.implode('|', $d).')/', $str);
-            $d = stripslashes($d[0]);
-        } else {
-            $a = explode($d, $str);
-        }
-
-        $i = 0;
-        $term = array();
-        foreach ($a as $key => $val) {
-            if (empty($term[$i])) {
-                $term[$i] = trim($val);
-
-                if ( ! (substr_count($term[$i], "'") & 1)) {
-                    $i++;
-                }
-            } else {
-                $term[$i] .= $d . trim($val);
-
-                if ( ! (substr_count($term[$i], "'") & 1)) {
-                    $i++;
-                }
-            }
-        }
-        return $term;
-    }
-
-    public function sqlExplode($str, $d = ' ', $e1 = '(', $e2 = ')')
-    {
-        if ($d == ' ') {
-            $d = array(' ', '\s');
-        }
-        if (is_array($d)) {
-            $d = array_map('preg_quote', $d);
-
-            if (in_array(' ', $d)) {
-                $d[] = '\s';
-            }
-
-            $split = '#(' . implode('|', $d) . ')#';
-
-            $str = preg_split($split, $str);
-            $d = stripslashes($d[0]);
-        } else {
-            $str = explode($d, $str);
-        }
-
-        $i = 0;
-        $term = array();
-
-        foreach ($str as $key => $val) {
-            if (empty($term[$i])) {
-                $term[$i] = trim($val);
-
-                $s1 = substr_count($term[$i], $e1);
-                $s2 = substr_count($term[$i], $e2);
-
-                if (strpos($term[$i], '(') !== false) {
-                    if ($s1 == $s2) {
-                        $i++;
-                    }
-                } else {
-                    if ( ! (substr_count($term[$i], "'") & 1) &&
-                         ! (substr_count($term[$i], "\"") & 1)) {
-                        $i++;
-                    }
-                }
-            } else {
-                $term[$i] .= $d . trim($val);
-                $c1 = substr_count($term[$i], $e1);
-                $c2 = substr_count($term[$i], $e2);
-
-                if (strpos($term[$i], '(') !== false) {
-                    if ($c1 == $c2) {
-                        $i++;
-                    }
-                } else {
-                    if ( ! (substr_count($term[$i], "'") & 1) &&
-                         ! (substr_count($term[$i], "\"") & 1)) {
-                        $i++;
-                    }
-                }
-            }
-        }
-        return $term;
-    }
-
-    public function clauseExplode($str, array $d, $e1 = '(', $e2 = ')')
-    {
-        if (is_array($d)) {
-            $d = array_map('preg_quote', $d);
-
-            if (in_array(' ', $d)) {
-                $d[] = '\s';
-            }
-
-            $split = '#(' . implode('|', $d) . ')#';
-
-            $str = preg_split($split, $str, -1, PREG_SPLIT_DELIM_CAPTURE);
-        }
-
-        $i = 0;
-        $term = array();
-
-        foreach ($str as $key => $val) {
-            if ($key & 1) {
-                if (isset($term[($i - 1)]) && ! is_array($term[($i - 1)])) {
-                    $term[($i - 1)] = array($term[($i - 1)], $val);
-                }
-                continue;
-            }
-            if (empty($term[$i])) {
-                $term[$i] = $val;
-            } else {
-                $term[$i] .= $str[($key - 1)] . $val;
-            }
-
-            $c1 = substr_count($term[$i], $e1);
-            $c2 = substr_count($term[$i], $e2);
-
-            if (strpos($term[$i], '(') !== false) {
-                if ($c1 == $c2) {
-                    $i++;
-                }
-            } else {
-                if ( ! (substr_count($term[$i], "'") & 1) &&
-                     ! (substr_count($term[$i], "\"") & 1)) {
-                    $i++;
-                }
-            }
-        }
-
-        if (isset($term[$i - 1])) {
-            $term[$i - 1] = array($term[$i - 1], '');
-        }
-
-        return $term;
-    }
-}
diff --git a/ipf/legacy_orm/orm/query/where.php b/ipf/legacy_orm/orm/query/where.php
deleted file mode 100644 (file)
index 1f51bbc..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-<?php
-
-class IPF_ORM_Query_Where extends IPF_ORM_Query_Condition
-{
-    public function load($where) 
-    {
-        $where = $this->_tokenizer->bracketTrim(trim($where));
-        $conn  = $this->query->getConnection();
-        $terms = $this->_tokenizer->sqlExplode($where);  
-
-        if (count($terms) > 1) {
-            if (substr($where, 0, 6) == 'EXISTS') {
-                return $this->parseExists($where, true);
-            } elseif (substr($where, 0, 10) == 'NOT EXISTS') {
-                return $this->parseExists($where, false);
-            }
-        }
-
-        if (count($terms) < 3) {
-            $terms = $this->_tokenizer->sqlExplode($where, array('=', '<', '<>', '>', '!='));
-        }
-
-        if (count($terms) > 1) {
-            $first = array_shift($terms);
-            $value = array_pop($terms);
-            $operator = trim(substr($where, strlen($first), -strlen($value)));
-            $table = null;
-            $field = null;
-
-            if (strpos($first, "'") === false && strpos($first, '(') === false) {
-                // normal field reference found
-                $a = explode('.', $first);
-        
-                $field = array_pop($a);
-                $reference = implode('.', $a);
-                
-                if (empty($reference)) {
-                    $map = $this->query->getRootDeclaration();  
-                    
-                    $alias = $this->query->getTableAlias($this->query->getRootAlias());
-                    $table = $map['table'];
-                } else {
-                    $map = $this->query->load($reference, false);
-    
-                    $alias = $this->query->getTableAlias($reference);
-                    $table = $map['table'];
-                }
-            }
-            $first = $this->query->parseClause($first);
-            
-            $sql = $first . ' ' . $operator . ' ' . $this->parseValue($value, $table, $field);
-        
-            return $sql;  
-        } else {
-            return $where;
-        }
-    }
-
-    public function parseValue($value, IPF_ORM_Table $table = null, $field = null)
-    {
-        $conn = $this->query->getConnection();
-
-        if (substr($value, 0, 1) == '(') {
-            // trim brackets
-            $trimmed = $this->_tokenizer->bracketTrim($value);
-
-            if (substr($trimmed, 0, 4) == 'FROM' ||
-                substr($trimmed, 0, 6) == 'SELECT') {
-
-                // subquery found
-                $q     = new IPF_ORM_Query();
-                $value = '(' . $this->query->createSubquery()->parseQuery($trimmed, false)->getQuery() . ')';
-
-            } elseif (substr($trimmed, 0, 4) == 'SQL:') {
-                $value = '(' . substr($trimmed, 4) . ')';
-            } else {
-                // simple in expression found
-                $e = $this->_tokenizer->sqlExplode($trimmed, ',');
-
-                $value = array();
-
-                $index = false;
-
-                foreach ($e as $part) {
-                    if (isset($table) && isset($field)) {
-                        $index = $table->enumIndex($field, trim($part, "'"));
-
-                        if (false !== $index && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
-                            $index = $conn->quote($index, 'text');
-                        }
-                    }
-
-                    if ($index !== false) {
-                        $value[] = $index;
-                    } else {
-                        $value[] = $this->parseLiteralValue($part);
-                    }
-                }
-
-                $value = '(' . implode(', ', $value) . ')';
-            }
-        } else if (substr($value, 0, 1) == ':' || $value === '?') {
-            // placeholder found
-            if (isset($table) && isset($field) && $table->getTypeOf($field) == 'enum') {
-                $this->query->addEnumParam($value, $table, $field);
-            } else {
-                $this->query->addEnumParam($value, null, null);
-            }
-        } else {
-            $enumIndex = false;
-            if (isset($table) && isset($field)) {
-                // check if value is enumerated value
-                $enumIndex = $table->enumIndex($field, trim($value, "'"));
-
-                if (false !== $enumIndex && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
-                    $enumIndex = $conn->quote($enumIndex, 'text');
-                }
-            }
-
-            if ($enumIndex !== false) {
-                $value = $enumIndex;
-            } else {
-                $value = $this->parseLiteralValue($value);
-            }
-        }
-        return $value;
-    }
-
-    public function parseExists($where, $negation)
-    {
-        $operator = ($negation) ? 'EXISTS' : 'NOT EXISTS';
-
-        $pos = strpos($where, '(');
-
-        if ($pos == false) {
-            throw new IPF_ORM_Exception('Unknown expression, expected a subquery with () -marks');
-        }
-
-        $sub = $this->_tokenizer->bracketTrim(substr($where, $pos));
-
-        return $operator . ' (' . $this->query->createSubquery()->parseQuery($sub, false)->getQuery() . ')';
-    }
-}
diff --git a/ipf/legacy_orm/orm/rawsql.php b/ipf/legacy_orm/orm/rawsql.php
deleted file mode 100644 (file)
index fce5016..0000000
+++ /dev/null
@@ -1,233 +0,0 @@
-<?php
-
-class IPF_ORM_RawSql extends IPF_ORM_Query_Abstract
-{
-    private $fields = array();
-
-    public function parseDqlQueryPart($queryPartName, $queryPart, $append=false)
-    {
-        if ($queryPartName == 'select') {
-            $this->_parseSelectFields($queryPart, $append);
-            return $this;
-        }
-
-        if (!isset($this->parts[$queryPartName])) {
-            $this->_sqlParts[$queryPartName] = array();
-        }
-
-        if (!$append) {
-            $this->_sqlParts[$queryPartName] = array($queryPart);
-        } else {
-            $this->_sqlParts[$queryPartName][] = $queryPart;
-        }
-        return $this;
-    }
-
-    protected function _addDqlQueryPart($queryPartName, $queryPart, $append=false)
-    {
-        return $this->parseDqlQueryPart($queryPartName, $queryPart, $append);
-    }
-
-    private function _parseSelectFields($queryPart, $append=false)
-    {
-        if ($append)
-            $this->fields[] = $queryPart;
-        else
-            $this->fields = array($queryPart);
-
-        $this->_sqlParts['select'] = array();
-    }
-
-    public function parseDqlQuery($query)
-    {
-        $this->_parseSelectFields($query);
-        $this->clear();
-
-        $tokens = $this->_tokenizer->sqlExplode($query, ' ');
-
-        $parts = array();
-        foreach ($tokens as $key => $part) {
-            $partLowerCase = strtolower($part);
-            switch ($partLowerCase) {
-                case 'select':
-                case 'from':
-                case 'where':
-                case 'limit':
-                case 'offset':
-                case 'having':
-                    $type = $partLowerCase;
-                    if ( ! isset($parts[$partLowerCase])) {
-                        $parts[$partLowerCase] = array();
-                    }
-                    break;
-                case 'order':
-                case 'group':
-                    $i = $key + 1;
-                    if (isset($tokens[$i]) && strtolower($tokens[$i]) === 'by') {
-                        $type = $partLowerCase . 'by';
-                        $parts[$type] = array();
-                    } else {
-                        //not a keyword so we add it to the previous type
-                        $parts[$type][] = $part;
-                    }
-                    break;
-                case 'by':
-                    continue;
-                default:
-                    //not a keyword so we add it to the previous type.
-                    if ( ! isset($parts[$type][0])) {
-                        $parts[$type][0] = $part;
-                    } else {
-                        // why does this add to index 0 and not append to the 
-                        // array. If it had done that one could have used 
-                        // parseQueryPart.
-                        $parts[$type][0] .= ' '.$part;
-                    }
-            }
-        }
-
-        $this->_sqlParts = $parts;
-        $this->_sqlParts['select'] = array();
-
-        return $this;
-    }
-
-    public function getSqlQuery($params=array())
-    {
-        $select = array();
-
-        foreach ($this->fields as $field) {
-            if (preg_match('/^{([^}{]+)\.([^}{]+)}$/U', $field, $e)) {
-                // try to auto-add component
-                if (!$this->hasSqlTableAlias($e[1])) {
-                    try {
-                        $this->addComponent($e[1], ucwords($e[1]));
-                    } catch (IPF_ORM_Exception $exception) {
-                        throw new IPF_ORM_Exception('The associated component for table alias ' . $e[1] . ' couldn\'t be found.');
-                    }
-                }
-
-                $componentAlias = $this->getComponentAlias($e[1]);
-                
-                if ($e[2] == '*') {
-                    foreach ($this->_queryComponents[$componentAlias]['table']->getColumnNames() as $name) {
-                        $field = $e[1] . '.' . $name;
-
-                        $select[$componentAlias][$field] = $field . ' AS ' . $e[1] . '__' . $name;
-                    }
-                } else {
-                    $field = $e[1] . '.' . $e[2];
-                    $select[$componentAlias][$field] = $field . ' AS ' . $e[1] . '__' . $e[2];
-                }
-            } else {
-                $select['__raw__'][] = $field;
-            }
-        }
-
-        // force-add all primary key fields
-
-        foreach ($this->getTableAliasMap() as $tableAlias => $componentAlias) {
-            $map = $this->_queryComponents[$componentAlias];
-
-            foreach ((array) $map['table']->getIdentifierColumnNames() as $key) {
-                $field = $tableAlias . '.' . $key;
-
-                if ( ! isset($this->_sqlParts['select'][$field])) {
-                    $select[$componentAlias][$field] = $field . ' AS ' . $tableAlias . '__' . $key;
-                }
-            }
-        }
-        
-        // first add the fields of the root component
-        reset($this->_queryComponents);
-        $componentAlias = key($this->_queryComponents);
-
-        $q = 'SELECT ' . implode(', ', $select[$componentAlias]);
-        unset($select[$componentAlias]);
-
-        foreach ($select as $component => $fields) {
-            if ( ! empty($fields)) {
-                $q .= ', ' . implode(', ', $fields);
-            }
-        }
-
-        $string = $this->getInheritanceCondition($this->getRootAlias());
-        if ( ! empty($string)) {
-            $this->_sqlParts['where'][] = $string;
-        }
-        $copy = $this->_sqlParts;
-        unset($copy['select']);
-
-        $q .= ( ! empty($this->_sqlParts['from']))?    ' FROM '     . implode(' ', $this->_sqlParts['from']) : '';
-        $q .= ( ! empty($this->_sqlParts['where']))?   ' WHERE '    . implode(' AND ', $this->_sqlParts['where']) : '';
-        $q .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby']) : '';
-        $q .= ( ! empty($this->_sqlParts['having']))?  ' HAVING '   . implode(' AND ', $this->_sqlParts['having']) : '';
-        $q .= ( ! empty($this->_sqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby']) : '';
-        $q .= ( ! empty($this->_sqlParts['limit']))?   ' LIMIT ' . implode(' ', $this->_sqlParts['limit']) : '';
-        $q .= ( ! empty($this->_sqlParts['offset']))?  ' OFFSET ' . implode(' ', $this->_sqlParts['offset']) : '';
-
-        if ( ! empty($string)) {
-            array_pop($this->_sqlParts['where']);
-        }
-        return $q;
-    }
-
-    public function getFields()
-    {
-        return $this->fields;
-    }
-
-    public function addComponent($tableAlias, $path)
-    {
-        $tmp           = explode(' ', $path);
-        $originalAlias = (count($tmp) > 1) ? end($tmp) : null;
-
-        $e = explode('.', $tmp[0]);
-
-        $fullPath = $tmp[0];
-        $fullLength = strlen($fullPath);
-
-        $table = null;
-
-        $currPath = '';
-
-        if (isset($this->_queryComponents[$e[0]])) {
-            $table = $this->_queryComponents[$e[0]]['table'];
-            $currPath = $parent = array_shift($e);
-        }
-
-        foreach ($e as $k => $component) {
-            // get length of the previous path
-            $length = strlen($currPath);
-
-            // build the current component path
-            $currPath = ($currPath) ? $currPath . '.' . $component : $component;
-
-            $delimeter = substr($fullPath, $length, 1);
-
-            // if an alias is not given use the current path as an alias identifier
-            if (strlen($currPath) === $fullLength && isset($originalAlias)) {
-                $componentAlias = $originalAlias;
-            } else {
-                $componentAlias = $currPath;
-            }
-
-            if (!isset($table)) {
-                $table = IPF_ORM_Manager::connection()->getTable($component);
-                $this->_queryComponents[$componentAlias] = array('table' => $table);
-            } else {
-                $relation = $table->getRelation($component);
-
-                $this->_queryComponents[$componentAlias] = array('table'    => $relation->getTable(),
-                                                                 'parent'   => $parent,
-                                                                 'relation' => $relation);
-            }
-            $this->addSqlTableAlias($tableAlias, $componentAlias);
-
-            $parent = $currPath;
-        }
-
-        return $this;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/record.php b/ipf/legacy_orm/orm/record.php
deleted file mode 100644 (file)
index 8be51ab..0000000
+++ /dev/null
@@ -1,1212 +0,0 @@
-<?php
-
-abstract class IPF_ORM_Record extends IPF_ORM_Record_Abstract implements Countable, IteratorAggregate, Serializable
-{
-    const STATE_DIRTY       = 1;
-    const STATE_TDIRTY      = 2;
-    const STATE_CLEAN       = 3;
-    const STATE_PROXY       = 4;
-    const STATE_TCLEAN      = 5;
-    const STATE_LOCKED     = 6;
-
-    protected $_id           = array();
-    protected $_data         = array();
-    protected $_values       = array();
-    protected $_state;
-    protected $_modified     = array();
-    protected $_errors = array();
-    protected $_references     = array();
-    protected $_pendingDeletes = array();
-    protected $_custom         = array();
-    private static $_index = 1;
-    private $_oid;
-
-    public function __construct($table = null, $isNewEntry = false)
-    {
-        if (isset($table) && $table instanceof IPF_ORM_Table) {
-            $this->_table = $table;
-            $exists = ( ! $isNewEntry);
-        } else {
-            // get the table of this class
-            $class = get_class($this);
-            $this->_table = IPF_ORM::getTable($class);
-            $exists = false;
-        }
-
-        // Check if the current connection has the records table in its registry
-        // If not this record is only used for creating table definition and setting up
-        // relations.
-        if ( ! $this->_table->getConnection()->hasTable($this->_table->getComponentName())) {
-            return;
-        }
-
-        $this->_oid = self::$_index;
-
-        self::$_index++;
-
-        // get the data array
-        $this->_data = $this->_table->getData();
-
-        // get the column count
-        $count = count($this->_data);
-
-        $this->_values = $this->cleanData($this->_data);
-
-        $this->prepareIdentifiers($exists);
-
-        if ( ! $exists) {
-            if ($count > count($this->_values)) {
-                $this->_state = IPF_ORM_Record::STATE_TDIRTY;
-            } else {
-                $this->_state = IPF_ORM_Record::STATE_TCLEAN;
-            }
-
-            // set the default values for this record
-            $this->assignDefaultValues();
-        } else {
-            $this->_state = IPF_ORM_Record::STATE_CLEAN;
-
-            if ($count < $this->_table->getColumnCount()) {
-                $this->_state  = IPF_ORM_Record::STATE_PROXY;
-            }
-        }
-
-        $repository = $this->_table->getRepository();
-        $repository->add($this);
-
-        $this->construct();
-    }
-
-    public static function _index()
-    {
-        return self::$_index;
-    }
-
-    public function construct(){}
-
-    public function getOid()
-    {
-        return $this->_oid;
-    }
-
-    public function oid()
-    {
-        return $this->_oid;
-    }
-
-    public function isValid()
-    {
-        // Clear the stack from any previous errors.
-        $this->_errors = array();
-
-        // Run validation process
-        $validator = new IPF_ORM_Validator();
-        $validator->validateRecord($this);
-        $this->validate();
-        if ($this->_state == self::STATE_TDIRTY || $this->_state == self::STATE_TCLEAN) {
-            $this->validateOnInsert();
-        } else {
-            $this->validateOnUpdate();
-        }
-
-        return count($this->_errors) === 0;
-    }
-
-    public function addError($invalidFieldName, $errorCode = 'general')
-    {
-        $this->_errors[$invalidFieldName][] = $errorCode;
-    }
-
-    public function getErrors()
-    {
-        return $this->_errors;
-    }
-
-    protected function validate(){}
-
-    protected function validateOnUpdate(){}
-
-    protected function validateOnInsert(){}
-
-    public function preSerialize($event){}
-
-    public function postSerialize($event){}
-
-    public function preUnserialize($event){}
-
-    public function postUnserialize($event){}
-
-    public function preSave($event){}
-
-    public function postSave($event){}
-
-    public function preDelete($event){}
-
-    public function postDelete($event){}
-
-    public function preUpdate($event){}
-
-    public function postUpdate($event){}
-
-    public function preInsert($event){}
-
-    public function postInsert($event){}
-
-    public function assignDefaultValues($overwrite = false)
-    {
-        if ( ! $this->_table->hasDefaultValues()) {
-            return false;
-        }
-        foreach ($this->_data as $column => $value) {
-            $default = $this->_table->getDefaultValueOf($column);
-
-            if ($default === null) {
-                continue;
-            }
-
-            if (IPF_ORM_Null::isNull($value) || $overwrite) {
-                $this->_data[$column] = $default;
-                $this->_modified[]    = $column;
-                $this->_state = IPF_ORM_Record::STATE_TDIRTY;
-            }
-        }
-    }
-
-    public function cleanData(&$data)
-    {
-        $tmp = $data;
-        $data = array();
-
-        foreach ($this->getTable()->getFieldNames() as $fieldName) {
-            if (isset($tmp[$fieldName])) {
-                $data[$fieldName] = $tmp[$fieldName];
-            } else if (array_key_exists($fieldName, $tmp)) {
-                $data[$fieldName] = IPF_ORM_Null::getInstance();
-            } else if (!isset($this->_data[$fieldName])) {
-                $data[$fieldName] = IPF_ORM_Null::getInstance();
-            }
-            unset($tmp[$fieldName]);
-        }
-
-        return $tmp;
-    }
-
-    public function hydrate(array $data)
-    {
-        $this->_values = array_merge($this->_values, $this->cleanData($data));
-        $this->_data   = array_merge($this->_data, $data);
-        $this->prepareIdentifiers(true);
-    }
-
-    private function prepareIdentifiers($exists = true)
-    {
-        switch ($this->_table->getIdentifierType()) {
-            case IPF_ORM::IDENTIFIER_AUTOINC:
-            case IPF_ORM::IDENTIFIER_NATURAL:
-                $name = $this->_table->getIdentifier();
-                if (is_array($name)) {
-                    $name = $name[0];
-                }
-                if ($exists) {
-                    if (isset($this->_data[$name]) && !IPF_ORM_Null::isNull($this->_data[$name])) {
-                        $this->_id[$name] = $this->_data[$name];
-                    }
-                }
-                break;
-            case IPF_ORM::IDENTIFIER_COMPOSITE:
-                $names = $this->_table->getIdentifier();
-
-                foreach ($names as $name) {
-                    if (IPF_ORM_Null::isNull($this->_data[$name])) {
-                        $this->_id[$name] = null;
-                    } else {
-                        $this->_id[$name] = $this->_data[$name];
-                    }
-                }
-                break;
-        }
-    }
-
-    public function serialize()
-    {
-        $event = new IPF_ORM_Event($this, IPF_ORM_Event::RECORD_SERIALIZE);
-
-        $this->preSerialize($event);
-
-        $vars = get_object_vars($this);
-
-        unset($vars['_references']);
-        unset($vars['_table']);
-        unset($vars['_errors']);
-        unset($vars['_filter']);
-
-        $name = $this->_table->getIdentifier();
-        $this->_data = array_merge($this->_data, $this->_id);
-
-        foreach ($this->_data as $k => $v) {
-            if ($v instanceof IPF_ORM_Record && $this->_table->getTypeOf($k) != 'object') {
-                unset($vars['_data'][$k]);
-            } elseif (IPF_ORM_Null::isNull($v)) {
-                unset($vars['_data'][$k]);
-            } else {
-                switch ($this->_table->getTypeOf($k)) {
-                    case 'array':
-                    case 'object':
-                        $vars['_data'][$k] = serialize($vars['_data'][$k]);
-                        break;
-                    case 'gzip':
-                        $vars['_data'][$k] = gzcompress($vars['_data'][$k]);
-                        break;
-                    case 'enum':
-                        $vars['_data'][$k] = $this->_table->enumIndex($k, $vars['_data'][$k]);
-                        break;
-                }
-            }
-        }
-
-        $str = serialize($vars);
-
-        $this->postSerialize($event);
-
-        return $str;
-    }
-
-    public function unserialize($serialized)
-    {
-        $event = new IPF_ORM_Event($this, IPF_ORM_Event::RECORD_UNSERIALIZE);
-
-        $this->preUnserialize($event);
-
-        $connection = IPF_ORM_Manager::connection();
-
-        $this->_oid = self::$_index;
-        self::$_index++;
-
-        $this->_table = $connection->getTable(get_class($this));
-
-        $array = unserialize($serialized);
-
-        foreach($array as $k => $v) {
-            $this->$k = $v;
-        }
-
-        foreach ($this->_data as $k => $v) {
-            switch ($this->_table->getTypeOf($k)) {
-                case 'array':
-                case 'object':
-                    $this->_data[$k] = unserialize($this->_data[$k]);
-                    break;
-                case 'gzip':
-                   $this->_data[$k] = gzuncompress($this->_data[$k]);
-                    break;
-                case 'enum':
-                    $this->_data[$k] = $this->_table->enumValue($k, $this->_data[$k]);
-                    break;
-
-            }
-        }
-
-        $this->_table->getRepository()->add($this);
-
-        $this->cleanData($this->_data);
-
-        $this->prepareIdentifiers($this->exists());
-
-        $this->postUnserialize($event);
-    }
-
-    public function state($state = null)
-    {
-        if ($state == null) {
-            return $this->_state;
-        }
-
-        $err = false;
-        if (is_integer($state)) {
-            if ($state >= 1 && $state <= 6) {
-                $this->_state = $state;
-            } else {
-                $err = true;
-            }
-        } else if (is_string($state)) {
-            $upper = strtoupper($state);
-
-            $const = 'IPF_ORM_Record::STATE_' . $upper;
-            if (defined($const)) {
-                $this->_state = constant($const);
-            } else {
-                $err = true;
-            }
-        }
-
-        if ($this->_state === IPF_ORM_Record::STATE_TCLEAN ||
-                $this->_state === IPF_ORM_Record::STATE_CLEAN) {
-            $this->_modified = array();
-        }
-
-        if ($err) {
-            throw new IPF_ORM_Exception('Unknown record state ' . $state);
-        }
-    }
-
-    public function refresh($deep = false)
-    {
-        $id = $this->identifier();
-        if ( ! is_array($id)) {
-            $id = array($id);
-        }
-        if (empty($id)) {
-            return false;
-        }
-        $id = array_values($id);
-
-        if ($deep) {
-            $query = $this->getTable()->createQuery();
-            foreach (array_keys($this->_references) as $name) {
-                $query->leftJoin(get_class($this) . '.' . $name);
-            }
-            $query->where(implode(' = ? AND ', $this->getTable()->getIdentifierColumnNames()) . ' = ?');
-            $this->clearRelated();
-            $record = $query->fetchOne($id);
-        } else {
-            // Use FETCH_ARRAY to avoid clearing object relations
-            $record = $this->getTable()->find($id, IPF_ORM::HYDRATE_ARRAY);
-            if ($record) {
-                $this->hydrate($record);
-            }
-        }
-
-        if ($record === false) {
-            throw new IPF_ORM_Exception('Failed to refresh. Record does not exist.');
-        }
-
-        $this->_modified = array();
-
-        $this->prepareIdentifiers();
-
-        $this->_state = IPF_ORM_Record::STATE_CLEAN;
-
-        return $this;
-    }
-
-    public function refreshRelated($name = null)
-    {
-        if (is_null($name)) {
-            foreach ($this->_table->getRelations() as $rel) {
-                $this->_references[$rel->getAlias()] = $rel->fetchRelatedFor($this);
-            }
-        } else {
-            $rel = $this->_table->getRelation($name);
-            $this->_references[$name] = $rel->fetchRelatedFor($this);
-        }
-    }
-
-    public function clearRelated()
-    {
-        $this->_references = array();
-    }
-
-    public function getTable()
-    {
-        return $this->_table;
-    }
-
-    public function getData()
-    {
-        return $this->_data;
-    }
-
-    public function rawGet($fieldName)
-    {
-        if ( ! isset($this->_data[$fieldName])) {
-            throw new IPF_ORM_Exception('Unknown property '. $fieldName);
-        }
-        if (IPF_ORM_Null::isNull($this->_data[$fieldName])) {
-            return null;
-        }
-
-        return $this->_data[$fieldName];
-    }
-
-    public function load()
-    {
-        // only load the data from database if the IPF_ORM_Record is in proxy state
-        if ($this->_state == IPF_ORM_Record::STATE_PROXY) {
-            $this->refresh();
-            $this->_state = IPF_ORM_Record::STATE_CLEAN;
-            return true;
-        }
-        return false;
-    }
-
-    public function get($fieldName, $load = true)
-    {
-        $value = IPF_ORM_Null::getInstance();
-
-        if (isset($this->_data[$fieldName])) {
-            if (IPF_ORM_Null::isNull($this->_data[$fieldName]) && $load) {
-                $this->load();
-            }
-            if (IPF_ORM_Null::isNull($this->_data[$fieldName])) {
-                $value = null;
-            } else {
-                $value = $this->_data[$fieldName];
-            }
-            return $value;
-        }
-
-        if (isset($this->_values[$fieldName])) {
-            return $this->_values[$fieldName];
-        }
-
-        if (!isset($this->_references[$fieldName]) && $load) {
-            $rel = $this->_table->getRelation($fieldName);
-            $this->_references[$fieldName] = $rel->fetchRelatedFor($this);
-        }
-        return $this->_references[$fieldName];
-    }
-
-    public function mapValue($name, $value)
-    {
-        $this->_values[$name] = $value;
-    }
-
-    public function set($fieldName, $value, $load = true)
-    {
-        if (isset($this->_data[$fieldName])) {
-            $type = $this->_table->getTypeOf($fieldName);
-            if ($value instanceof IPF_ORM_Record) {
-                $id = $value->getIncremented();
-
-                if ($id !== null && $type !== 'object') {
-                    $value = $id;
-                }
-            }
-
-            if ($load) {
-                $old = $this->get($fieldName, $load);
-            } else {
-                $old = $this->_data[$fieldName];
-            }
-
-            if ($this->_isValueModified($type, $old, $value)) {
-                if ($value === null) {
-                    $value = IPF_ORM_Null::getInstance();
-                }
-
-                $this->_data[$fieldName] = $value;
-                $this->_modified[] = $fieldName;
-                switch ($this->_state) {
-                    case IPF_ORM_Record::STATE_CLEAN:
-                        $this->_state = IPF_ORM_Record::STATE_DIRTY;
-                        break;
-                    case IPF_ORM_Record::STATE_TCLEAN:
-                        $this->_state = IPF_ORM_Record::STATE_TDIRTY;
-                        break;
-                }
-            }
-        } else {
-            $this->coreSetRelated($fieldName, $value);
-        }
-
-        return $this;
-    }
-
-    protected function _isValueModified($type, $old, $new)
-    {
-        if ($type == 'boolean' && (is_bool($old) || is_numeric($old)) && (is_bool($new) || is_numeric($new)) && $old == $new) {
-            return false;
-        } else {
-            return $old !== $new;
-        }
-    }
-
-    public function coreSetRelated($name, $value)
-    {
-        $rel = $this->_table->getRelation($name);
-
-        if ($value === null) {
-            $value = IPF_ORM_Null::getInstance();
-        }
-
-        // one-to-many or one-to-one relation
-        if ($rel instanceof IPF_ORM_Relation_ForeignKey || $rel instanceof IPF_ORM_Relation_LocalKey) {
-            if ( ! $rel->isOneToOne()) {
-                // one-to-many relation found
-                if ( ! ($value instanceof IPF_ORM_Collection)) {
-                    throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Collection when setting one-to-many references.");
-                }
-                if (isset($this->_references[$name])) {
-                    $this->_references[$name]->setData($value->getData());
-                    return $this;
-                }
-            } else {
-                if (!IPF_ORM_Null::isNull($value)) {
-                    $relatedTable = $value->getTable();
-                    $foreignFieldName = $relatedTable->getFieldName($rel->getForeign());
-                    $localFieldName = $this->_table->getFieldName($rel->getLocal());
-
-                    // one-to-one relation found
-                    if ( ! ($value instanceof IPF_ORM_Record)) {
-                        throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Record or IPF_ORM_Null when setting one-to-one references.");
-                    }
-                    if ($rel instanceof IPF_ORM_Relation_LocalKey) {
-                        if ( ! empty($foreignFieldName) && $foreignFieldName != $value->getTable()->getIdentifier()) {
-                            $this->set($localFieldName, $value->rawGet($foreignFieldName), false);
-                        } else {
-                            $this->set($localFieldName, $value, false);
-                        }
-                    } else {
-                        $value->set($foreignFieldName, $this, false);
-                    }
-                }
-            }
-
-        } else if ($rel instanceof IPF_ORM_Relation_Association) {
-            // join table relation found
-            if ( ! ($value instanceof IPF_ORM_Collection)) {
-                throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Collection when setting many-to-many references.");
-            }
-        }
-
-        $this->_references[$name] = $value;
-    }
-
-    public function contains($fieldName)
-    {
-        if (isset($this->_data[$fieldName])) {
-            // this also returns true if the field is a IPF_ORM_Null.
-            // imho this is not correct behavior.
-            return true;
-        }
-        if (isset($this->_id[$fieldName])) {
-            return true;
-        }
-        if (isset($this->_values[$fieldName])) {
-            return true;
-        }
-        if (isset($this->_references[$fieldName]) && !IPF_ORM_Null::isNull($this->_references[$fieldName])) {
-            return true;
-        }
-        return false;
-    }
-
-    public function __unset($name)
-    {
-        if (isset($this->_data[$name])) {
-            $this->_data[$name] = array();
-        } else if (isset($this->_references[$name])) {
-            if ($this->_references[$name] instanceof IPF_ORM_Record) {
-                $this->_pendingDeletes[] = $this->$name;
-                $this->_references[$name] = IPF_ORM_Null::getInstance();
-            } elseif ($this->_references[$name] instanceof IPF_ORM_Collection) {
-                $this->_pendingDeletes[] = $this->$name;
-                $this->_references[$name]->setData(array());
-            }
-        }
-    }
-
-    public function getPendingDeletes()
-    {
-        return $this->_pendingDeletes;
-    }
-
-    public function save(IPF_ORM_Connection $conn = null)
-    {
-        if ($conn === null) {
-            $conn = $this->_table->getConnection();
-        }
-        $conn->unitOfWork->saveGraph($this);
-    }
-
-    public function trySave(IPF_ORM_Connection $conn = null) {
-        try {
-            $this->save($conn);
-            return true;
-        } catch (IPF_ORM_Exception_Validator $ignored) {
-            return false;
-        }
-    }
-
-    public function replace(IPF_ORM_Connection $conn = null)
-    {
-        if ($conn === null) {
-            $conn = $this->_table->getConnection();
-        }
-
-        if ($this->exists()) {
-            return $this->save();
-        } else {
-            $identifier = (array) $this->getTable()->getIdentifier();
-            return $conn->replace($this->_table, $this->toArray(), $identifier);
-        }
-    }
-
-    public function getModified()
-    {
-        $a = array();
-
-        foreach ($this->_modified as $k => $v) {
-            $a[$v] = $this->_data[$v];
-        }
-        return $a;
-    }
-
-    public function modifiedFields()
-    {
-        $a = array();
-
-        foreach ($this->_modified as $k => $v) {
-            $a[$v] = $this->_data[$v];
-        }
-        return $a;
-    }
-
-    public function getPrepared(array $array = array())
-    {
-        $a = array();
-
-        if (empty($array)) {
-            $modifiedFields = $this->_modified;
-        }
-
-        foreach ($modifiedFields as $field) {
-            $type = $this->_table->getTypeOf($field);
-
-            if (IPF_ORM_Null::isNull($this->_data[$field])) {
-                $a[$field] = null;
-                continue;
-            }
-
-            switch ($type) {
-                case 'array':
-                case 'object':
-                    $a[$field] = serialize($this->_data[$field]);
-                    break;
-                case 'gzip':
-                    $a[$field] = gzcompress($this->_data[$field],5);
-                    break;
-                case 'boolean':
-                    $a[$field] = $this->getTable()->getConnection()->convertBooleans($this->_data[$field]);
-                break;
-                case 'enum':
-                    $a[$field] = $this->_table->enumIndex($field, $this->_data[$field]);
-                    break;
-                default:
-                    if ($this->_data[$field] instanceof IPF_ORM_Record) {
-                        $a[$field] = $this->_data[$field]->getIncremented();
-                        if ($a[$field] !== null) {
-                            $this->_data[$field] = $a[$field];
-                        }
-                    } else {
-                        $a[$field] = $this->_data[$field];
-                    }
-                    /** TODO:
-                    if ($this->_data[$v] === null) {
-                        throw new IPF_ORM_Record_Exception('Unexpected null value.');
-                    }
-                    */
-            }
-        }
-        $map = $this->_table->inheritanceMap;
-        foreach ($map as $k => $v) {
-            $k = $this->_table->getFieldName($k);
-            $old = $this->get($k, false);
-
-            if ((string) $old !== (string) $v || $old === null) {
-                $a[$k] = $v;
-                $this->_data[$k] = $v;
-            }
-        }
-
-        return $a;
-    }
-
-    public function count()
-    {
-        return count($this->_data);
-    }
-
-    public function columnCount()
-    {
-        return $this->count();
-    }
-
-    public function toArray($deep = true, $prefixKey = false)
-    {
-        if ($this->_state == self::STATE_LOCKED) {
-            return false;
-        }
-
-        $stateBeforeLock = $this->_state;
-        $this->_state = self::STATE_LOCKED;
-
-        $a = array();
-
-        foreach ($this as $column => $value) {
-            if (IPF_ORM_Null::isNull($value) || is_object($value)) {
-                $value = null;
-            }
-
-            $a[$column] = $value;
-        }
-
-        if ($this->_table->getIdentifierType() ==  IPF_ORM::IDENTIFIER_AUTOINC) {
-            $i      = $this->_table->getIdentifier();
-            $a[$i]  = $this->getIncremented();
-        }
-
-        if ($deep) {
-            foreach ($this->_references as $key => $relation) {
-                if (!IPF_ORM_Null::isNull($relation)) {
-                    $a[$key] = $relation->toArray($deep, $prefixKey);
-                }
-            }
-        }
-
-        // [FIX] Prevent mapped IPF_ORM_Records from being displayed fully
-        foreach ($this->_values as $key => $value) {
-            if ($value instanceof IPF_ORM_Record) {
-                $a[$key] = $value->toArray($deep, $prefixKey);
-            } else {
-                $a[$key] = $value;
-            }
-        }
-
-        $this->_state = $stateBeforeLock;
-
-        return $a;
-    }
-
-    public function merge($data, $deep = true)
-    {
-        if ($data instanceof $this) {
-            $array = $data->toArray($deep);
-        } else if (is_array($data)) {
-            $array = $data;
-        }
-
-        return $this->fromArray($array, $deep);
-    }
-
-    public function fromArray(array $array, $deep = true)
-    {
-        $refresh = false;
-        foreach ($array as $key => $value) {
-            if ($key == '_identifier') {
-                $refresh = true;
-                $this->assignIdentifier((array) $value);
-                continue;
-            }
-
-            if ($deep && $this->getTable()->hasRelation($key)) {
-                $this->$key->fromArray($value, $deep);
-            } else if ($this->getTable()->hasField($key)) {
-                $this->set($key, $value);
-            }
-        }
-
-        if ($refresh) {
-            $this->refresh();
-        }
-    }
-
-    protected function _synchronizeWithArrayForRelation($key, $value)
-    {
-        $this->get($key)->synchronizeWithArray($value);
-    }
-
-    public function synchronizeWithArray(array $array, $deep = true)
-    {
-        $refresh = false;
-        foreach ($array as $key => $value) {
-            if ($key == '_identifier') {
-                $refresh = true;
-                $this->assignIdentifier((array) $value);
-                continue;
-            }
-            if ($deep && $this->getTable()->hasRelation($key)) {
-                $this->_synchronizeWithArrayForRelation($key, $value);
-            } else if ($this->getTable()->hasField($key)) {
-                $this->set($key, $value);
-            }
-        }
-        // eliminate relationships missing in the $array
-        foreach ($this->_references as $name => $obj) {
-            if ( ! isset($array[$name])) {
-                unset($this->$name);
-            }
-        }
-        if ($refresh) {
-            $this->refresh();
-        }
-    }
-
-    public function exists()
-    {
-        return ($this->_state !== IPF_ORM_Record::STATE_TCLEAN &&
-                $this->_state !== IPF_ORM_Record::STATE_TDIRTY);
-    }
-
-    public function isModified()
-    {
-        return ($this->_state === IPF_ORM_Record::STATE_DIRTY ||
-                $this->_state === IPF_ORM_Record::STATE_TDIRTY);
-    }
-
-    public function hasRelation($fieldName)
-    {
-        if (isset($this->_data[$fieldName]) || isset($this->_id[$fieldName])) {
-            return true;
-        }
-        return $this->_table->hasRelation($fieldName);
-    }
-
-    public function getIterator()
-    {
-        return new IPF_ORM_Record_Iterator($this);
-    }
-
-    public function delete(IPF_ORM_Connection $conn = null)
-    {
-        if ($conn == null) {
-            $conn = $this->_table->getConnection();
-        }
-        return $conn->unitOfWork->delete($this);
-    }
-
-    public function copy($deep = false)
-    {
-        $data = $this->_data;
-
-        if ($this->_table->getIdentifierType() === IPF_ORM::IDENTIFIER_AUTOINC) {
-            $id = $this->_table->getIdentifier();
-
-            unset($data[$id]);
-        }
-
-        $ret = $this->_table->create($data);
-        $modified = array();
-
-        foreach ($data as $key => $val) {
-            if (!IPF_ORM_Null::isNull($val)) {
-                $ret->_modified[] = $key;
-            }
-        }
-
-        if ($deep) {
-            foreach ($this->_references as $key => $value) {
-                if ($value instanceof IPF_ORM_Collection) {
-                    foreach ($value as $record) {
-                        $ret->{$key}[] = $record->copy($deep);
-                    }
-                } else if($value instanceof IPF_ORM_Record) {
-                    $ret->set($key, $value->copy($deep));
-                }
-            }
-        }
-
-        return $ret;
-    }
-
-    public function assignIdentifier($id = false)
-    {
-        if ($id === false) {
-            $this->_id       = array();
-            $this->_data     = $this->cleanData($this->_data);
-            $this->_state    = IPF_ORM_Record::STATE_TCLEAN;
-            $this->_modified = array();
-        } elseif ($id === true) {
-            $this->prepareIdentifiers(true);
-            $this->_state    = IPF_ORM_Record::STATE_CLEAN;
-            $this->_modified = array();
-        } else {
-            if (is_array($id)) {
-                foreach ($id as $fieldName => $value) {
-                    $this->_id[$fieldName] = $value;
-                    $this->_data[$fieldName] = $value;
-                }
-            } else {
-                $name = $this->_table->getIdentifier();
-                $this->_id[$name] = $id;
-                $this->_data[$name] = $id;
-            }
-            $this->_state = IPF_ORM_Record::STATE_CLEAN;
-            $this->_modified = array();
-        }
-    }
-
-    public function identifier()
-    {
-        return $this->_id;
-    }
-
-    final public function getIncremented()
-    {
-        $id = current($this->_id);
-        if ($id === false) {
-            return null;
-        }
-
-        return $id;
-    }
-
-    public function pk($sep='_')
-    {
-        $pk = '';
-        foreach($this->_id as $val) {
-            if ($pk!='')
-                $pk .= $sep;
-            $pk .= $val;
-        }
-        return $pk;
-    }
-
-    public function getLast()
-    {
-        return $this;
-    }
-
-    public function hasReference($name)
-    {
-        return isset($this->_references[$name]);
-    }
-
-    public function reference($name)
-    {
-        if (isset($this->_references[$name])) {
-            return $this->_references[$name];
-        }
-    }
-
-    public function obtainReference($name)
-    {
-        if (isset($this->_references[$name])) {
-            return $this->_references[$name];
-        }
-        throw new IPF_ORM_Exception("Unknown reference $name");
-    }
-
-    public function getReferences()
-    {
-        return $this->_references;
-    }
-
-    final public function setRelated($alias, IPF_ORM_Access $coll)
-    {
-        $this->_references[$alias] = $coll;
-    }
-
-    public function loadReference($name)
-    {
-        $rel = $this->_table->getRelation($name);
-        $this->_references[$name] = $rel->fetchRelatedFor($this);
-    }
-
-    public function call($callback, $column)
-    {
-        $args = func_get_args();
-        array_shift($args);
-
-        if (isset($args[0])) {
-            $fieldName = $args[0];
-            $args[0] = $this->get($fieldName);
-
-            $newvalue = call_user_func_array($callback, $args);
-
-            $this->_data[$fieldName] = $newvalue;
-        }
-        return $this;
-    }
-
-    public function unlink($alias, $ids = array())
-    {
-        $ids = (array) $ids;
-
-        $q = new IPF_ORM_Query();
-
-        $rel = $this->getTable()->getRelation($alias);
-
-        if ($rel instanceof IPF_ORM_Relation_Association) {
-            $q->delete()
-              ->from($rel->getAssociationTable()->getComponentName())
-              ->where($rel->getLocal() . ' = ?', array_values($this->identifier()));
-
-            if (count($ids) > 0) {
-                $q->whereIn($rel->getForeign(), $ids);
-            }
-
-            $q->execute();
-
-        } else if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
-            $q->update($rel->getTable()->getComponentName())
-              ->set($rel->getForeign(), '?', array(null))
-              ->addWhere($rel->getForeign() . ' = ?', array_values($this->identifier()));
-
-            if (count($ids) > 0) {
-                $q->whereIn($rel->getTable()->getIdentifier(), $ids);
-            }
-
-            $q->execute();
-        }
-        if (isset($this->_references[$alias])) {
-            foreach ($this->_references[$alias] as $k => $record) {
-                if (in_array(current($record->identifier()), $ids)) {
-                    $this->_references[$alias]->remove($k);
-                }
-            }
-            $this->_references[$alias]->takeSnapshot();
-        }
-        return $this;
-    }
-
-    public function link($alias, $ids)
-    {
-        $ids = (array) $ids;
-
-        if ( ! count($ids)) {
-            return $this;
-        }
-
-        $identifier = array_values($this->identifier());
-        $identifier = array_shift($identifier);
-
-        $rel = $this->getTable()->getRelation($alias);
-
-        if ($rel instanceof IPF_ORM_Relation_Association) {
-
-            $modelClassName = $rel->getAssociationTable()->getComponentName();
-            $localFieldName = $rel->getLocalFieldName();
-            $localFieldDef  = $rel->getAssociationTable()->getColumnDefinition($localFieldName);
-            if ($localFieldDef['type'] == 'integer') {
-                $identifier = (integer) $identifier;
-            }
-            $foreignFieldName = $rel->getForeignFieldName();
-            $foreignFieldDef  = $rel->getAssociationTable()->getColumnDefinition($foreignFieldName);
-            if ($foreignFieldDef['type'] == 'integer') {
-                for ($i = 0; $i < count($ids); $i++) {
-                    $ids[$i] = (integer) $ids[$i];
-                }
-            }
-
-            foreach ($ids as $id) {
-                $record = new $modelClassName;
-                $record[$localFieldName]   = $identifier;
-                $record[$foreignFieldName] = $id;
-                $record->save();
-            }
-
-        } else if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
-
-            $q = new IPF_ORM_Query();
-
-            $q->update($rel->getTable()->getComponentName())
-              ->set($rel->getForeign(), '?', array_values($this->identifier()));
-
-            if (count($ids) > 0) {
-                $q->whereIn($rel->getTable()->getIdentifier(), $ids);
-            }
-
-            $q->execute();
-
-        } else if ($rel instanceof IPF_ORM_Relation_LocalKey) {
-
-            $q = new IPF_ORM_Query();
-
-            $q->update($this->getTable()->getComponentName())
-              ->set($rel->getLocalFieldName(), '?', $ids);
-
-            if (count($ids) > 0) {
-                $q->whereIn($rel->getTable()->getIdentifier(), array_values($this->identifier()));
-            }
-
-            $q->execute();
-
-        }
-
-        return $this;
-    }
-
-    public function __call($method, $args)
-    {
-        if (($template = $this->_table->getMethodOwner($method)) !== false) {
-            $template->setInvoker($this);
-            return call_user_func_array(array($template, $method), $args);
-        }
-
-        foreach ($this->_table->getTemplates() as $template) {
-            if (method_exists($template, $method)) {
-                $template->setInvoker($this);
-                $this->_table->setMethodOwner($method, $template);
-
-                return call_user_func_array(array($template, $method), $args);
-            }
-        }
-
-        throw new IPF_ORM_Exception(sprintf('Unknown method %s::%s', get_class($this), $method));
-    }
-
-    public function free($deep = false)
-    {
-        if ($this->_state != self::STATE_LOCKED) {
-            $this->_state = self::STATE_LOCKED;
-
-            $this->_table->getRepository()->evict($this->_oid);
-            $this->_table->removeRecord($this);
-            $this->_data = array();
-            $this->_id = array();
-
-            if ($deep) {
-                foreach ($this->_references as $name => $reference) {
-                    if (!IPF_ORM_Null::isNull($reference)) {
-                        $reference->free($deep);
-                    }
-                }
-            }
-
-            $this->_references = array();
-        }
-    }
-
-    public function toString()
-    {
-        return IPF_ORM::dump(get_object_vars($this));
-    }
-
-    public function __toString()
-    {
-        return sprintf('<%s #%d>', get_class($this), $this->_oid);
-    }
-
-    public function SetFromFormData($cleaned_values)
-    {
-        $names = $this->_table->getFieldNames();
-        foreach ($cleaned_values as $key=>$val) {
-            $validators = $this->getTable()->getFieldValidators($key);
-            if (
-                array_key_exists('image',$validators) ||
-                array_key_exists('file',$validators) ||
-                array_key_exists('email',$validators)
-            ){
-                if (($val!==null) && ($val==''))
-                    continue;
-            }
-            if (array_search($key,$names)){
-                $this->$key = $val;
-            }
-        }
-    }
-
-    public function SetCustom($name, $val)
-    {
-        $this->_custom[$name] = $val;
-    }
-
-    public function GetCustom($name)
-    {
-        if (isset($this->_custom[$name]))
-            return $this->_custom[$name];
-        return null;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/record/abstract.php b/ipf/legacy_orm/orm/record/abstract.php
deleted file mode 100644 (file)
index 07aab0d..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php
-
-abstract class IPF_ORM_Record_Abstract extends IPF_ORM_Access
-{
-    protected $_table;
-
-    public static function setTableDefinition(IPF_ORM_Table $table)
-    {
-    }
-
-    public static function setUp(IPF_ORM_Table $table)
-    {
-    }
-
-    public function getTable()
-    {
-        return $this->_table;
-    }
-
-    public function setInheritanceMap($map)
-    {
-        $this->_table->setOption('inheritanceMap', $map);
-    }
-
-    public function attribute($attr, $value)
-    {
-        if ($value == null) {
-            if (is_array($attr)) {
-                foreach ($attr as $k => $v) {
-                    $this->_table->setAttribute($k, $v);
-                }
-            } else {
-                return $this->_table->getAttribute($attr);
-            }
-        } else {
-            $this->_table->setAttribute($attr, $value);
-        }    
-    }
-
-    public function check($constraint, $name = null)
-    {
-        if (is_array($constraint)) {
-            foreach ($constraint as $name => $def) {
-                $this->_table->addCheckConstraint($def, $name);
-            }
-        } else {
-            $this->_table->addCheckConstraint($constraint, $name);
-        }
-        return $this;
-    }
-
-    public function __debugInfo()
-    {
-        $r = array();
-        foreach ($this->getTable()->getColumnNames() as $column)
-            $r[$column] = $this->get($column);
-        return $r;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/record/iterator.php b/ipf/legacy_orm/orm/record/iterator.php
deleted file mode 100644 (file)
index 505a990..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-class IPF_ORM_Record_Iterator extends ArrayIterator
-{
-    private $record;
-
-    public function __construct(IPF_ORM_Record $record)
-    {
-        $this->record = $record;
-        parent::__construct($record->getData());
-    }
-
-    public function current()
-    {
-        $value = parent::current();
-
-        if (IPF_ORM_Null::isNull($value)) {
-            return null;
-        } else {
-            return $value;
-        }
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/relation.php b/ipf/legacy_orm/orm/relation.php
deleted file mode 100644 (file)
index 3d0c550..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-<?php
-
-abstract class IPF_ORM_Relation implements ArrayAccess
-{
-    const ONE_AGGREGATE         = 0;
-    const ONE_COMPOSITE         = 1;
-    const MANY_AGGREGATE        = 2;
-    const MANY_COMPOSITE        = 3;
-
-    const ONE   = 0;
-    const MANY  = 2;
-
-    protected $definition = array('alias'       => true,
-                                  'foreign'     => true,
-                                  'local'       => true,
-                                  'class'       => true,
-                                  'type'        => true,
-                                  'table'       => true,
-                                  'localTable'  => true,
-                                  'name'        => null,
-                                  'refTable'    => null,
-                                  'onDelete'    => null,
-                                  'onUpdate'    => null,
-                                  'deferred'    => null,
-                                  'deferrable'  => null,
-                                  'constraint'  => null,
-                                  'equal'       => false,
-                                  'cascade'     => array(), // application-level cascades
-                                  'owningSide'  => false, // whether this is the owning side
-                                  'exclude'     => false,
-                                  );
-
-    public function __construct(array $definition)
-    {
-        $def = array();
-        foreach ($this->definition as $key => $val) {
-            if ( ! isset($definition[$key]) && $val) {
-                throw new IPF_ORM_Exception($key . ' is required!');
-            }
-            if (isset($definition[$key])) {
-                $def[$key] = $definition[$key];
-            } else {
-                $def[$key] = $this->definition[$key];
-            }
-        }
-        $this->definition = $def;
-    }
-
-    public function hasConstraint()
-    {
-        return $this->definition['constraint'] ||
-               $this->definition['onUpdate'] ||
-               $this->definition['onDelete'];
-    }
-
-    public function isDeferred()
-    {
-        return $this->definition['deferred'];
-    }
-
-    public function isDeferrable()
-    {
-        return $this->definition['deferrable'];
-    }
-    public function isEqual()
-    {
-        return $this->definition['equal'];
-    }
-
-    public function offsetExists($offset)
-    {
-        return isset($this->definition[$offset]);
-    }
-
-    public function offsetGet($offset)
-    {
-        if (isset($this->definition[$offset])) {
-            return $this->definition[$offset];
-        }
-
-        return null;
-    }
-
-    public function offsetSet($offset, $value)
-    {
-        if (isset($this->definition[$offset])) {
-            $this->definition[$offset] = $value;
-        }
-    }
-
-    public function offsetUnset($offset)
-    {
-        $this->definition[$offset] = false;
-    }
-
-    public function toArray()
-    {
-        return $this->definition;
-    }
-
-    final public function getAlias()
-    {
-        return $this->definition['alias'];
-    }
-
-    final public function getType()
-    {
-        return $this->definition['type'];
-    }
-
-    public function isCascadeDelete()
-    {
-        return in_array('delete', $this->definition['cascade']);
-    }
-
-    final public function getTable()
-    {
-        return IPF_ORM_Manager::connection()->getTable($this->definition['class']);
-    }
-
-    final public function getClass()
-    {
-        return $this->definition['class'];
-    }
-
-    final public function getLocal()
-    {
-        return $this->definition['local'];
-    }
-
-    final public function getLocalFieldName()
-    {
-        return $this->definition['localTable']->getFieldName($this->definition['local']);
-    }
-
-    final public function getForeign()
-    {
-        return $this->definition['foreign'];
-    }
-
-    final public function getForeignFieldName()
-    {
-        return $this->definition['table']->getFieldName($this->definition['foreign']);
-    }
-
-    final public function isComposite()
-    {
-        return ($this->definition['type'] == IPF_ORM_Relation::ONE_COMPOSITE ||
-                $this->definition['type'] == IPF_ORM_Relation::MANY_COMPOSITE);
-    }
-
-    final public function isOneToOne()
-    {
-        return ($this->definition['type'] == IPF_ORM_Relation::ONE_AGGREGATE ||
-                $this->definition['type'] == IPF_ORM_Relation::ONE_COMPOSITE);
-    }
-
-    abstract public function getRelationDql($count, $context);
-
-    abstract public function fetchRelatedFor(IPF_ORM_Record $record);
-
-    public function __toString()
-    {
-        $r[] = "<pre>";
-        foreach ($this->definition as $k => $v) {
-            if (is_object($v)) {
-                $v = 'Object(' . get_class($v) . ')';
-            }
-            $r[] = $k . ' : ' . $v;
-        }
-        $r[] = "</pre>";
-        return implode("\n", $r);
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/relation/association.php b/ipf/legacy_orm/orm/relation/association.php
deleted file mode 100644 (file)
index 29fc2b2..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-class IPF_ORM_Relation_Association extends IPF_ORM_Relation
-{
-    public function getAssociationFactory()
-    {
-        return $this->definition['refTable'];
-    }
-    public function getAssociationTable()
-    {
-        return $this->definition['refTable'];
-    }
-
-    public function getRelationDql($count, $context)
-    {
-        $component = $this->definition['refTable']->getComponentName();
-        $thisTable = $this->getTable()->getComponentName();
-
-        $sub = substr(str_repeat('?, ', $count), 0, -2);
-        switch ($context) {
-            case "record":
-                $dql = 'FROM ' . $thisTable . '.' . $component .
-                      ' WHERE ' . $thisTable . '.' . $component . '.' . $this->definition['local'] . ' IN (' . $sub . ')';
-                break;
-            case "collection":
-                $dql = 'FROM ' . $component . '.' . $thisTable .
-                      ' WHERE ' . $component . '.' . $this->definition['local'] . ' IN (' . $sub . ')';
-                break;
-        }
-        return $dql;
-    }
-
-    public function fetchRelatedFor(IPF_ORM_Record $record)
-    {
-        $id = $record->pk();
-        if (empty($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
-            $coll = new IPF_ORM_Collection($this->getTable());
-        } else {
-            $coll = $this->getTable()->getConnection()->query($this->getRelationDql(1, 'record'), array($id));
-        }
-        $coll->setReference($record, $this);
-        return $coll;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/relation/foreignkey.php b/ipf/legacy_orm/orm/relation/foreignkey.php
deleted file mode 100644 (file)
index 5e5a91f..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php
-
-class IPF_ORM_Relation_ForeignKey extends IPF_ORM_Relation
-{
-    public function fetchRelatedFor(IPF_ORM_Record $record)
-    {
-        $id = array();
-        $localTable = $record->getTable();
-        foreach ((array) $this->definition['local'] as $local) {
-           $value = $record->get($localTable->getFieldName($local));
-           if (isset($value)) {
-               $id[] = $value;
-           }
-        }
-        if ($this->isOneToOne()) {
-            if ( ! $record->exists() || empty($id) || 
-                 ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
-                
-                $related = $this->getTable()->create();
-            } else {
-                $dql  = 'FROM ' . $this->getTable()->getComponentName()
-                      . ' WHERE ' . $this->getCondition();
-
-                $coll = $this->getTable()->getConnection()->query($dql, $id);
-                $related = $coll[0];
-            }
-
-            $related->set($related->getTable()->getFieldName($this->definition['foreign']),
-                    $record, false);
-        } else {
-
-            if ( ! $record->exists() || empty($id) || 
-                 ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
-                
-                $related = new IPF_ORM_Collection($this->getTable());
-            } else {
-                $query      = $this->getRelationDql(1, '');
-                $related    = $this->getTable()->getConnection()->query($query, $id);
-            }
-            $related->setReference($record, $this);
-        }
-        return $related;
-    }
-
-    public function getRelationDql($count, $context)
-    {
-        $table = $this->getTable();
-        $component = $table->getComponentName();
-
-        $dql  = 'FROM ' . $component
-              . ' WHERE ' . $component . '.' . $this->definition['foreign']
-              . ' IN (' . substr(str_repeat('?, ', $count), 0, -2) . ')';
-
-        $ordering = $table->getOrdering();
-        if ($ordering)
-            $dql .= ' ORDER BY ' . implode(', ', $ordering);
-
-        return $dql;
-    }
-
-    public function getCondition($alias = null)
-    {
-        if ( ! $alias) {
-           $alias = $this->getTable()->getComponentName();
-        }
-        $conditions = array();
-        foreach ((array) $this->definition['foreign'] as $foreign) {
-            $conditions[] = $alias . '.' . $foreign . ' = ?';
-        }
-        return implode(' AND ', $conditions);
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/relation/localkey.php b/ipf/legacy_orm/orm/relation/localkey.php
deleted file mode 100644 (file)
index edd225f..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-
-class IPF_ORM_Relation_LocalKey extends IPF_ORM_Relation
-{
-    public function fetchRelatedFor(IPF_ORM_Record $record)
-    {
-        $localFieldName = $record->getTable()->getFieldName($this->definition['local']);
-        $id = $record->get($localFieldName);
-
-        if (is_null($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
-            $related = $this->getTable()->create();
-        } else {
-            $dql  = 'FROM ' . $this->getTable()->getComponentName()
-                 . ' WHERE ' . $this->getCondition();
-
-            $related = $this->getTable()
-                            ->getConnection()
-                            ->query($dql, array($id))
-                            ->getFirst();
-            
-            if ( ! $related || empty($related)) {
-                $related = $this->getTable()->create();
-            }
-        }
-
-        $record->set($localFieldName, $related, false);
-
-        return $related;
-    }
-
-    public function getRelationDql($count, $context)
-    {
-        $component = $this->getTable()->getComponentName();
-        return 'FROM ' . $component .
-              ' WHERE ' . $component . '.' . $this->definition['foreign'] .
-              ' IN (' . substr(str_repeat('?, ', $count), 0, -2) . ')';
-    }
-
-    public function getCondition($alias = null)
-    {
-        if ( ! $alias) {
-           $alias = $this->getTable()->getComponentName();
-        }
-        return $alias . '.' . $this->definition['foreign'] . ' = ?';
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/relation/nest.php b/ipf/legacy_orm/orm/relation/nest.php
deleted file mode 100644 (file)
index 9e27245..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-
-class IPF_ORM_Relation_Nest extends IPF_ORM_Relation_Association
-{
-    public function getRelationDql($count, $context = 'record')
-    {
-        switch ($context) {
-            case 'record':
-                $identifierColumnNames = $this->definition['table']->getIdentifierColumnNames();
-                $identifier = array_pop($identifierColumnNames);
-                $sub    = 'SELECT '.$this->definition['foreign'] 
-                        . ' FROM '.$this->definition['refTable']->getTableName()
-                        . ' WHERE '.$this->definition['local']
-                        . ' = ?';
-
-                $sub2   = 'SELECT '.$this->definition['local']
-                        . ' FROM '.$this->definition['refTable']->getTableName()
-                        . ' WHERE '.$this->definition['foreign']
-                        . ' = ?';
-
-                $dql  = 'FROM ' . $this->definition['table']->getComponentName()
-                      . '.' . $this->definition['refTable']->getComponentName()
-                      . ' WHERE ' . $this->definition['table']->getComponentName()
-                      . '.' . $identifier 
-                      . ' IN (' . $sub . ')'
-                      . ' || ' . $this->definition['table']->getComponentName() 
-                      . '.' . $identifier
-                      . ' IN (' . $sub2 . ')';
-                break;
-            case 'collection':
-                $sub  = substr(str_repeat('?, ', $count),0,-2);
-                $dql  = 'FROM '.$this->definition['refTable']->getComponentName()
-                      . '.' . $this->definition['table']->getComponentName()
-                      . ' WHERE '.$this->definition['refTable']->getComponentName()
-                      . '.' . $this->definition['local'] . ' IN (' . $sub . ')';
-        };
-
-        return $dql;
-    }
-
-    public function fetchRelatedFor(IPF_ORM_Record $record)
-    {
-        $id = $record->getIncremented();
-
-
-        if (empty($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
-            return new IPF_ORM_Collection($this->getTable());
-        } else {
-            $q = new IPF_ORM_RawSql($this->getTable()->getConnection());
-
-            $assocTable = $this->getAssociationFactory()->getTableName();
-            $tableName  = $record->getTable()->getTableName();
-            $identifierColumnNames = $record->getTable()->getIdentifierColumnNames();
-            $identifier = array_pop($identifierColumnNames);
-    
-            $sub = 'SELECT ' . $this->getForeign()
-                 . ' FROM ' . $assocTable 
-                 . ' WHERE ' . $this->getLocal() 
-                 . ' = ?';
-
-            $condition[] = $tableName . '.' . $identifier . ' IN (' . $sub . ')';
-            $joinCondition[] = $tableName . '.' . $identifier . ' = ' . $assocTable . '.' . $this->getForeign();
-
-            if ($this->definition['equal']) {
-                $sub2   = 'SELECT ' . $this->getLocal()
-                        . ' FROM '  . $assocTable
-                        . ' WHERE ' . $this->getForeign()
-                        . ' = ?';
-
-                $condition[] = $tableName . '.' . $identifier . ' IN (' . $sub2 . ')';
-                $joinCondition[] = $tableName . '.' . $identifier . ' = ' . $assocTable . '.' . $this->getLocal();
-            }
-            $q->select('{'.$tableName.'.*}')
-              ->addSelect('{'.$assocTable.'.*}')
-              ->from($tableName . ' INNER JOIN ' . $assocTable . ' ON ' . implode(' OR ', $joinCondition))
-              ->where(implode(' OR ', $condition));
-            $q->addComponent($tableName,  $record->getTable()->getComponentName());
-            $q->addComponent($assocTable, $record->getTable()->getComponentName(). '.' . $this->getAssociationFactory()->getComponentName());
-
-            $params = ($this->definition['equal']) ? array($id, $id) : array($id);
-
-            return $q->execute($params);
-        }
-    }
-}
diff --git a/ipf/legacy_orm/orm/relation/parser.php b/ipf/legacy_orm/orm/relation/parser.php
deleted file mode 100644 (file)
index ce1fe89..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-<?php
-
-class IPF_ORM_Relation_Parser
-{
-    protected $_table;
-    protected $_relations = array();
-    protected $_pending   = array();
-
-    public function __construct(IPF_ORM_Table $table)
-    {
-        $this->_table = $table;
-    }
-
-    public function getTable()
-    {
-        return $this->_table;
-    }
-
-    public function getPendingRelation($name)
-    {
-        if ( ! isset($this->_pending[$name])) {
-            throw new IPF_ORM_Exception('Unknown pending relation ' . $name);
-        }
-
-        return $this->_pending[$name];
-    }
-
-    public function getPendingRelations()
-    {
-        return $this->_pending;
-    }
-
-    public function unsetPendingRelations($name)
-    {
-       unset($this->_pending[$name]);
-    }
-
-    public function hasRelation($name)
-    {
-        return isset($this->_pending[$name]) || isset($this->_relations[$name]);
-    }
-
-    public function bind($name, $alias, $type, $options=array())
-    {
-        if (!$alias)
-            $alias = $name;
-
-        unset($this->relations[$alias]);
-
-        $options['class'] = $name;
-        $options['alias'] = $alias;
-        $options['type']  = $type;
-
-        $this->_pending[$alias] = $options;
-    }
-
-    private function createRelation($alias)
-    {
-        if (isset($this->_relations[$alias]))
-            return;
-
-        if (!isset($this->_pending[$alias]))
-            throw new IPF_ORM_Exception('Unknown relation alias "' . $alias . '".');
-
-        $def = $this->_pending[$alias];
-        $identifierColumnNames = $this->_table->getIdentifierColumnNames();
-        $idColumnName = array_pop($identifierColumnNames);
-
-        // check if reference class name exists
-        // if it does we are dealing with association relation
-        if (isset($def['refClass'])) {
-            $def = $this->completeAssocDefinition($def);
-            $localClasses = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName()));
-
-            if ( ! isset($this->_pending[$def['refClass']]) &&
-                 ! isset($this->_relations[$def['refClass']])) {
-
-                $parser = $def['refTable']->getRelationParser();
-                if ( ! $parser->hasRelation($this->_table->getComponentName())) {
-                    $parser->bind($this->_table->getComponentName(), null, IPF_ORM_Relation::ONE, array(
-                        'local'     => $def['local'],
-                        'foreign'   => $idColumnName,
-                        'localKey'  => true,
-                    ));
-                }
-
-                if (!$this->hasRelation($def['refClass'])) {
-                    $this->bind($def['refClass'], null, IPF_ORM_Relation::MANY, array(
-                        'foreign' => $def['local'],
-                        'local'   => $idColumnName,
-                    ));
-                }
-            }
-            if (in_array($def['class'], $localClasses)) {
-                $rel = new IPF_ORM_Relation_Nest($def);
-            } else {
-                $rel = new IPF_ORM_Relation_Association($def);
-            }
-        } else {
-            // simple foreign key relation
-            $def = $this->completeDefinition($def);
-
-            if (isset($def['localKey'])) {
-                $rel = new IPF_ORM_Relation_LocalKey($def);
-
-                // Automatically index foreign keys which are not primary
-                $foreign = (array) $def['foreign'];
-                foreach ($foreign as $fk) {
-                    if ( ! $rel->getTable()->isIdentifier($fk)) {
-                        $rel->getTable()->addIndex($fk, array('fields' => array($fk)));
-                    }
-                }
-            } else {
-                $rel = new IPF_ORM_Relation_ForeignKey($def);
-            }
-        }
-
-        if (isset($rel)) {
-            // unset pending relation
-            unset($this->_pending[$alias]);
-
-            $this->_relations[$alias] = $rel;
-        }
-    }
-
-    public function getRelation($alias)
-    {
-        $this->getRelations();
-        return $this->_relations[$alias];
-    }
-
-    public function getRelations()
-    {
-        foreach ($this->_pending as $k => $v) {
-            $this->createRelation($k);
-        }
-
-        return $this->_relations;
-    }
-
-    public function completeAssocDefinition($def)
-    {
-        $conn = $this->_table->getConnection();
-
-        $def['table']       = IPF_ORM::getTable($def['class']);
-        $def['localTable']  = $this->_table;
-        $def['class']       = $def['table']->getComponentName();
-        $def['refTable']    = IPF_ORM::getTable($def['refClass']);
-
-        $id = $def['refTable']->getIdentifierColumnNames();
-
-        if (count($id) > 1) {
-            if ( ! isset($def['foreign'])) {
-                // foreign key not set
-                // try to guess the foreign key
-
-                $def['foreign'] = ($def['local'] === $id[0]) ? $id[1] : $id[0];
-            }
-            if ( ! isset($def['local'])) {
-                // foreign key not set
-                // try to guess the foreign key
-
-                $def['local'] = ($def['foreign'] === $id[0]) ? $id[1] : $id[0];
-            }
-        } else {
-
-            if ( ! isset($def['foreign'])) {
-                // foreign key not set
-                // try to guess the foreign key
-
-                $columns = $this->getIdentifiers($def['table']);
-
-                $def['foreign'] = $columns;
-            }
-            if ( ! isset($def['local'])) {
-                // local key not set
-                // try to guess the local key
-                $columns = $this->getIdentifiers($this->_table);
-
-                $def['local'] = $columns;
-            }
-        }
-        return $def;
-    }
-
-    public function getIdentifiers(IPF_ORM_Table $table)
-    {
-        $componentNameToLower = strtolower($table->getComponentName());
-        if (is_array($table->getIdentifier())) {
-            $columns = array();
-            foreach ((array) $table->getIdentifierColumnNames() as $identColName) {
-                $columns[] = $componentNameToLower . '_' . $identColName;
-            }
-        } else {
-            $columns = $componentNameToLower . '_' . $table->getColumnName(
-                    $table->getIdentifier());
-        }
-
-        return $columns;
-    }
-
-    public function guessColumns(array $classes, IPF_ORM_Table $foreignTable)
-    {
-        $conn = $this->_table->getConnection();
-
-        foreach ($classes as $class) {
-            try {
-                $table   = $conn->getTable($class);
-            } catch (IPF_ORM_Exception $e) {
-                continue;
-            }
-            $columns = $this->getIdentifiers($table);
-            $found   = true;
-
-            foreach ((array) $columns as $column) {
-                if ( ! $foreignTable->hasColumn($column)) {
-                    $found = false;
-                    break;
-                }
-            }
-            if ($found) {
-                break;
-            }
-        }
-
-        if ( ! $found) {
-            throw new IPF_ORM_Exception("Couldn't find columns.");
-        }
-
-        return $columns;
-    }
-
-    public function completeDefinition($def)
-    {
-        $conn = $this->_table->getConnection();
-        $def['table']       = IPF_ORM::getTable($def['class']);
-        $def['localTable']  = $this->_table;
-        $def['class']       = $def['table']->getComponentName();
-
-        $foreignClasses = array_merge($def['table']->getOption('parents'), array($def['class']));
-        $localClasses   = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName()));
-
-        $localIdentifierColumnNames = $this->_table->getIdentifierColumnNames();
-        $localIdentifierCount = count($localIdentifierColumnNames);
-        $localIdColumnName = array_pop($localIdentifierColumnNames);
-        $foreignIdentifierColumnNames = $def['table']->getIdentifierColumnNames();
-        $foreignIdColumnName = array_pop($foreignIdentifierColumnNames);
-
-        if (isset($def['local'])) {
-            if ( ! isset($def['foreign'])) {
-                // local key is set, but foreign key is not
-                // try to guess the foreign key
-
-                if ($def['local'] === $localIdColumnName) {
-                    $def['foreign'] = $this->guessColumns($localClasses, $def['table']);
-                } else {
-                    // the foreign field is likely to be the
-                    // identifier of the foreign class
-                    $def['foreign'] = $foreignIdColumnName;
-                    $def['localKey'] = true;
-                }
-            } else {
-                if ($localIdentifierCount == 1) {
-                    if ($def['local'] == $localIdColumnName && isset($def['owningSide'])
-                            && $def['owningSide'] === true) {
-                        $def['localKey'] = true;
-                    } else if (($def['local'] !== $localIdColumnName && $def['type'] == IPF_ORM_Relation::ONE)) {
-                        $def['localKey'] = true;
-                    }
-                } else if ($localIdentifierCount > 1) {
-                    // It's a composite key and since 'foreign' can not point to a composite
-                    // key currently, we know that 'local' must be the foreign key.
-                    $def['localKey'] = true;
-                }
-            }
-        } else {
-            if (isset($def['foreign'])) {
-                // local key not set, but foreign key is set
-                // try to guess the local key
-                if ($def['foreign'] === $foreignIdColumnName) {
-                    $def['localKey'] = true;
-                    try {
-                        $def['local'] = $this->guessColumns($foreignClasses, $this->_table);
-                    } catch (IPF_ORM_Exception $e) {
-                        $def['local'] = $localIdColumnName;
-                    }
-                } else {
-                    $def['local'] = $localIdColumnName;
-                }
-            } else {
-                // neither local or foreign key is being set
-                // try to guess both keys
-
-                $conn = $this->_table->getConnection();
-
-                // the following loops are needed for covering inheritance
-                foreach ($localClasses as $class) {
-                    $table = $conn->getTable($class);
-                    $identifierColumnNames = $table->getIdentifierColumnNames();
-                    $idColumnName = array_pop($identifierColumnNames);
-                    $column = strtolower($table->getComponentName())
-                            . '_' . $idColumnName;
-
-                    foreach ($foreignClasses as $class2) {
-                        $table2 = $conn->getTable($class2);
-                        if ($table2->hasColumn($column)) {
-                            $def['foreign'] = $column;
-                            $def['local'] = $idColumnName;
-                            return $def;
-                        }
-                    }
-                }
-
-                foreach ($foreignClasses as $class) {
-                    $table  = $conn->getTable($class);
-                    $identifierColumnNames = $table->getIdentifierColumnNames();
-                    $idColumnName = array_pop($identifierColumnNames);
-                    $column = strtolower($table->getComponentName())
-                            . '_' . $idColumnName;
-
-                    foreach ($localClasses as $class2) {
-                        $table2 = $conn->getTable($class2);
-                        if ($table2->hasColumn($column)) {
-                            $def['foreign']  = $idColumnName;
-                            $def['local']    = $column;
-                            $def['localKey'] = true;
-                            return $def;
-                        }
-                    }
-                }
-
-                // auto-add columns and auto-build relation
-                $columns = array();
-                foreach ((array) $this->_table->getIdentifierColumnNames() as $id) {
-                    // ?? should this not be $this->_table->getComponentName() ??
-                    $column = strtolower($table->getComponentName())
-                            . '_' . $id;
-
-                    $col = $this->_table->getColumnDefinition($id);
-                    $type = $col['type'];
-                    $length = $col['length'];
-
-                    unset($col['type']);
-                    unset($col['length']);
-                    unset($col['autoincrement']);
-                    unset($col['primary']);
-
-                    $def['table']->setColumn($column, $type, $length, $col);
-
-                    $columns[] = $column;
-                }
-                if (count($columns) > 1) {
-                    $def['foreign'] = $columns;
-                } else {
-                    $def['foreign'] = $columns[0];
-                }
-                $def['local'] = $localIdColumnName;
-            }
-        }
-        return $def;
-    }
-}
diff --git a/ipf/legacy_orm/orm/table.php b/ipf/legacy_orm/orm/table.php
deleted file mode 100644 (file)
index 6208103..0000000
+++ /dev/null
@@ -1,1033 +0,0 @@
-<?php
-
-class IPF_ORM_Table extends IPF_ORM_Configurable implements Countable
-{
-    protected $_data             = array();
-    protected $_identifier = array();
-    protected $_identifierType;
-    protected $_conn;
-    protected $_identityMap        = array();
-
-    protected $_repository;
-    protected $_columns          = array();
-    protected $_fieldNames    = array();
-
-    protected $_columnNames = array();
-
-    protected $hasDefaultValues;
-
-    protected $_options      = array('name'           => null,
-                                     'tableName'      => null,
-                                     'inheritanceMap' => array(),
-                                     'enumMap'        => array(),
-                                     'type'           => null,
-                                     'charset'        => null,
-                                     'collation'      => null,
-                                     'indexes'        => array(),
-                                     'parents'        => array(),
-                                     'queryParts'     => array(),
-                                     'versioning'     => null,
-                                     'subclasses'     => array(),
-                                     );
-
-    protected $_ordering = null;
-
-    protected $_parser;
-
-    protected $_templates   = array();
-    protected $_invokedMethods = array();
-
-    public $listeners = array();
-
-    public function __construct($name, IPF_ORM_Connection $conn)
-    {
-        if (empty($name) || !class_exists($name))
-            throw new IPF_ORM_Exception("Couldn't find class " . $name);
-
-        $this->_conn = $conn;
-        $this->setParent($this->_conn);
-
-        $this->_options['name'] = $name;
-        $this->_parser = new IPF_ORM_Relation_Parser($this);
-
-        $this->initParents($name);
-
-        // create database table
-        $name::setTableDefinition($this);
-
-        if (!isset($this->_options['tableName'])) {
-            $this->setTableName(IPF_ORM_Inflector::tableize($class->getName()));
-        }
-
-        $this->initIdentifier();
-
-        $name::setUp($this);
-
-        $this->_repository = new IPF_ORM_Table_Repository($this);
-    }
-
-    private function initParents($name)
-    {
-        $names = array();
-
-        $class = $name;
-        do {
-            if ($class === 'IPF_ORM_Record')
-                break;
-
-            $name = $class;
-            $names[] = $name;
-        } while ($class = get_parent_class($class));
-
-        if ($class === false)
-            throw new IPF_ORM_Exception('Class "' . $name . '" must be a child class of IPF_ORM_Record');
-
-        // reverse names
-        $names = array_reverse($names);
-        // save parents
-        array_pop($names);
-        $this->_options['parents'] = $names;
-    }
-
-    public function initIdentifier()
-    {
-        switch (count($this->_identifier)) {
-            case 0:
-                $definition = array('type' => 'integer',
-                                    'length' => 20,
-                                    'autoincrement' => true,
-                                    'primary' => true);
-                $this->setColumn('id', $definition['type'], $definition['length'], $definition, true);
-                $this->_identifier = 'id';
-                $this->_identifierType = IPF_ORM::IDENTIFIER_AUTOINC;
-                break;
-            case 1:
-                foreach ($this->_identifier as $pk) {
-                    $e = $this->getDefinitionOf($pk);
-
-                    $found = false;
-
-                    foreach ($e as $option => $value) {
-                        if ($found) {
-                            break;
-                        }
-
-                        $e2 = explode(':', $option);
-
-                        switch (strtolower($e2[0])) {
-                            case 'autoincrement':
-                            case 'autoinc':
-                                if ($value !== false) {
-                                    $this->_identifierType = IPF_ORM::IDENTIFIER_AUTOINC;
-                                    $found = true;
-                                }
-                                break;
-                        }
-                    }
-                    if ( ! isset($this->_identifierType)) {
-                        $this->_identifierType = IPF_ORM::IDENTIFIER_NATURAL;
-                    }
-                }
-
-                $this->_identifier = $pk;
-
-                break;
-            default:
-                $this->_identifierType = IPF_ORM::IDENTIFIER_COMPOSITE;
-        }
-    }
-
-    public function isIdentifier($fieldName)
-    {
-        return ($fieldName === $this->getIdentifier() ||
-                in_array($fieldName, (array) $this->getIdentifier()));
-    }
-
-    public function isIdentifierAutoincrement()
-    {
-        return $this->getIdentifierType() === IPF_ORM::IDENTIFIER_AUTOINC;
-    }
-
-    public function isIdentifierComposite()
-    {
-        return $this->getIdentifierType() === IPF_ORM::IDENTIFIER_COMPOSITE;
-    }
-
-    public function getMethodOwner($method)
-    {
-        return (isset($this->_invokedMethods[$method])) ?
-                      $this->_invokedMethods[$method] : false;
-    }
-
-    public function setMethodOwner($method, $class)
-    {
-        $this->_invokedMethods[$method] = $class;
-    }
-
-    public function getTemplates()
-    {
-        return $this->_templates;
-    }
-
-    public function getRelationParser()
-    {
-        return $this->_parser;
-    }
-
-    public function __get($option)
-    {
-        if (isset($this->_options[$option])) {
-            return $this->_options[$option];
-        }
-        return null;
-    }
-
-    public function __isset($option)
-    {
-        return isset($this->_options[$option]);
-    }
-
-    public function getOptions()
-    {
-        return $this->_options;
-    }
-
-    public function setOptions($options)
-    {
-        foreach ($options as $key => $value) {
-            $this->setOption($key, $value);
-        }
-    }
-
-    public function addCheckConstraint($definition, $name)
-    {
-        if (is_string($name)) {
-            $this->_options['checks'][$name] = $definition;
-        } else {
-            $this->_options['checks'][] = $definition;
-        }
-
-        return $this;
-    }
-
-    public function addIndex($index, array $definition)
-    {
-        $this->_options['indexes'][$index] = $definition;
-    }
-
-    public function getIndex($index)
-    {
-        if (isset($this->_options['indexes'][$index])) {
-            return $this->_options['indexes'][$index];
-        }
-
-        return false;
-    }
-
-    public function bind($class, $alias, $type, array $options)
-    {
-        $this->_parser->bind($class, $alias, $type, $options);
-    }
-
-    public function ownsOne($class, $alias, $options=array())
-    {
-        $this->bind($class, $alias, IPF_ORM_Relation::ONE_COMPOSITE, $options);
-    }
-
-    public function ownsMany($class, $alias, $options=array())
-    {
-        $this->bind($class, $alias, IPF_ORM_Relation::MANY_COMPOSITE, $options);
-    }
-
-    public function hasOne($class, $alias, $options=array())
-    {
-        $this->bind($class, $alias, IPF_ORM_Relation::ONE_AGGREGATE, $options);
-    }
-
-    public function hasMany($class, $alias, $options=array())
-    {
-        $this->bind($class, $alias, IPF_ORM_Relation::MANY_AGGREGATE, $options);
-    }
-
-    public function hasRelation($alias)
-    {
-        return $this->_parser->hasRelation($alias);
-    }
-
-    public function getRelation($alias)
-    {
-        return $this->_parser->getRelation($alias);
-    }
-
-    public function getRelations()
-    {
-        return $this->_parser->getRelations();
-    }
-
-    public function createQuery($alias = '')
-    {
-        if ( ! empty($alias)) {
-            $alias = ' ' . trim($alias);
-        }
-        return IPF_ORM_Query::create($this->_conn)->from($this->getComponentName() . $alias);
-    }
-
-    public function getRepository()
-    {
-        return $this->_repository;
-    }
-
-    public function setOption($name, $value)
-    {
-        switch ($name) {
-            case 'name':
-            case 'tableName':
-                break;
-            case 'enumMap':
-            case 'inheritanceMap':
-            case 'index':
-                if (!is_array($value))
-                    throw new IPF_ORM_Exception($name . ' should be an array.');
-                break;
-        }
-        $this->_options[$name] = $value;
-    }
-
-    public function getOption($name, $default=null)
-    {
-        if (isset($this->_options[$name])) {
-            return $this->_options[$name];
-        }
-        return $default;
-    }
-
-    public function getColumnName($fieldName)
-    {
-        // FIX ME: This is being used in places where an array is passed, but it should not be an array
-        // For example in places where IPF_ORM should support composite foreign/primary keys
-        $fieldName = is_array($fieldName) ? $fieldName[0]:$fieldName;
-
-        if (isset($this->_columnNames[$fieldName])) {
-            return $this->_columnNames[$fieldName];
-        }
-
-        return strtolower($fieldName);
-    }
-
-    public function getColumnDefinition($columnName, $default=false)
-    {
-        if (isset($this->_columns[$columnName])) {
-            return $this->_columns[$columnName];
-        }
-        return false;
-    }
-
-    public function getFieldName($columnName)
-    {
-        if (isset($this->_fieldNames[$columnName])) {
-            return $this->_fieldNames[$columnName];
-        }
-        return $columnName;
-    }
-    public function setColumns(array $definitions)
-    {
-        foreach ($definitions as $name => $options) {
-            $this->setColumn($name, $options['type'], $options['length'], $options);
-        }
-    }
-
-    public function setColumn($name, $type, $length = null, $options = array(), $prepend = false)
-    {
-        if (is_string($options)) {
-            $options = explode('|', $options);
-        }
-
-        foreach ($options as $k => $option) {
-            if (is_numeric($k)) {
-                if (!empty($option)) {
-                    $options[$option] = true;
-                }
-                unset($options[$k]);
-            }
-        }
-
-        // extract column name & field name
-        if (stripos($name, ' as '))
-        {
-            if (strpos($name, ' as')) {
-                $parts = explode(' as ', $name);
-            } else {
-                $parts = explode(' AS ', $name);
-            }
-
-            if (count($parts) > 1) {
-                $fieldName = $parts[1];
-            } else {
-                $fieldName = $parts[0];
-            }
-
-            $name = strtolower($parts[0]);
-        } else {
-            $fieldName = $name;
-            $name = strtolower($name);
-        }
-
-        $name = trim($name);
-        $fieldName = trim($fieldName);
-
-        if ($prepend) {
-            $this->_columnNames = array_merge(array($fieldName => $name), $this->_columnNames);
-            $this->_fieldNames = array_merge(array($name => $fieldName), $this->_fieldNames);
-        } else {
-            $this->_columnNames[$fieldName] = $name;
-            $this->_fieldNames[$name] = $fieldName;
-        }
-
-        if ($length == null) {
-            switch ($type) {
-                case 'string':
-                case 'clob':
-                case 'float':
-                case 'double':
-                case 'integer':
-                case 'array':
-                case 'object':
-                case 'blob':
-                case 'gzip':
-                    // use php int max
-                    $length = 2147483647;
-                break;
-                case 'boolean':
-                    $length = 1;
-                case 'date':
-                    // YYYY-MM-DD ISO 8601
-                    $length = 10;
-                case 'time':
-                    // HH:NN:SS+00:00 ISO 8601
-                    $length = 14;
-                case 'timestamp':
-                    // YYYY-MM-DDTHH:MM:SS+00:00 ISO 8601
-                    $length = 25;
-                break;
-            }
-        }
-
-        $options['type'] = $type;
-        $options['length'] = $length;
-
-        if ($prepend) {
-            $this->_columns = array_merge(array($name => $options), $this->_columns);
-        } else {
-            $this->_columns[$name] = $options;
-        }
-
-        if (isset($options['primary']) && $options['primary']) {
-            if (isset($this->_identifier)) {
-                $this->_identifier = (array) $this->_identifier;
-            }
-            if ( ! in_array($fieldName, $this->_identifier)) {
-                $this->_identifier[] = $fieldName;
-            }
-        }
-        if (isset($options['default'])) {
-            $this->hasDefaultValues = true;
-        }
-    }
-
-    public function hasDefaultValues()
-    {
-        return $this->hasDefaultValues;
-    }
-
-    public function getDefaultValueOf($fieldName)
-    {
-        $columnName = $this->getColumnName($fieldName);
-        if ( ! isset($this->_columns[$columnName])) {
-            throw new IPF_ORM_Exception("Couldn't get default value. Column ".$columnName." doesn't exist.");
-        }
-        if (isset($this->_columns[$columnName]['default'])) {
-            return $this->_columns[$columnName]['default'];
-        } else {
-            return null;
-        }
-    }
-
-    public function getIdentifier()
-    {
-        return $this->_identifier;
-    }
-
-    public function getIdentifierType()
-    {
-        return $this->_identifierType;
-    }
-
-    public function hasColumn($columnName)
-    {
-        return isset($this->_columns[strtolower($columnName)]);
-    }
-
-    public function hasField($fieldName)
-    {
-        return isset($this->_columnNames[$fieldName]);
-    }
-
-    public function setSubClasses(array $map)
-    {
-        $class = $this->getComponentName();
-        if (isset($map[$class])) {
-            $this->setOption('inheritanceMap', $map[$class]);
-        } else {
-            $this->setOption('subclasses', array_keys($map));
-        }
-    }
-
-    public function setConnection(IPF_ORM_Connection $conn)
-    {
-        $this->_conn = $conn;
-
-        $this->setParent($this->_conn);
-
-        return $this;
-    }
-
-    public function getConnection()
-    {
-        return $this->_conn;
-    }
-
-    public function create(array $array = array())
-    {
-        $record = new $this->_options['name']($this, true);
-        $record->fromArray($array);
-
-        return $record;
-    }
-
-    public function find($id, $hydrationMode = null)
-    {
-        if (is_null($id)) {
-            return false;
-        }
-
-        $id = is_array($id) ? array_values($id) : array($id);
-        
-        $q = $this->createQuery();
-        $q->where(implode(' = ? AND ', (array) $this->getIdentifier()) . ' = ?', $id)
-                ->limit(1);
-        $res = $q->fetchOne(array(), $hydrationMode);
-        $q->free();
-        
-        return $res;
-    }
-
-    public function findAll($hydrationMode = null)
-    {
-        $q = $this->createQuery();
-        if ($this->_ordering) {
-            $q->orderBy(implode(', ', $this->_ordering));
-        } elseif ($this->hasTemplate('IPF_ORM_Template_Orderable')) {
-            $q->orderBy($this->getTemplate('IPF_ORM_Template_Orderable')->getColumnName());
-        }
-        return $q->execute(array(), $hydrationMode);
-    }
-
-    public function findBySql($dql, $params = array(), $hydrationMode = null)
-    {
-        return $this->createQuery()->where($dql)->execute($params, $hydrationMode);
-    }
-
-    public function findByDql($dql, $params = array(), $hydrationMode = null)
-    {
-        $parser = new IPF_ORM_Query($this->_conn);
-        $component = $this->getComponentName();
-        $query = 'FROM ' . $component . ' WHERE ' . $dql;
-
-        return $parser->query($query, $params, $hydrationMode);
-    }
-
-    public function findOneByDql($dql, $params = array(), $hydrationMode = null)
-    {
-       $results = $this->findByDql($dql, $params, $hydrationMode);
-        if (is_array($results) && isset($results[0])) {
-            return $results[0];
-        } else if ($results instanceof IPF_ORM_Collection && $results->count() > 0) {
-            return $results->getFirst();
-        } else {
-            return false;
-        }
-    }
-    
-    
-    public function execute($queryKey, $params = array(), $hydrationMode = IPF_ORM::HYDRATE_RECORD)
-    {
-        return IPF_ORM_Manager::getInstance()
-                            ->getQueryRegistry()
-                            ->get($queryKey, $this->getComponentName())
-                            ->execute($params, $hydrationMode);
-    }
-
-    public function executeOne($queryKey, $params = array(), $hydrationMode = IPF_ORM::HYDRATE_RECORD)
-    {
-        return IPF_ORM_Manager::getInstance()
-                            ->getQueryRegistry()
-                            ->get($queryKey, $this->getComponentName())
-                            ->fetchOne($params, $hydrationMode);
-    }
-
-    public function clear()
-    {
-        $this->_identityMap = array();
-    }
-
-    public function addRecord(IPF_ORM_Record $record)
-    {
-        $id = implode(' ', $record->identifier());
-
-        if (isset($this->_identityMap[$id])) {
-            return false;
-        }
-
-        $this->_identityMap[$id] = $record;
-
-        return true;
-    }
-
-    public function removeRecord(IPF_ORM_Record $record)
-    {
-        $id = implode(' ', $record->identifier());
-
-        if (isset($this->_identityMap[$id])) {
-            unset($this->_identityMap[$id]);
-            return true;
-        }
-
-        return false;
-    }
-
-    public function getRecord()
-    {
-        if ( ! empty($this->_data)) {
-            $identifierFieldNames = $this->getIdentifier();
-
-            if ( ! is_array($identifierFieldNames)) {
-                $identifierFieldNames = array($identifierFieldNames);
-            }
-
-            $found = false;
-            foreach ($identifierFieldNames as $fieldName) {
-                if ( ! isset($this->_data[$fieldName])) {
-                    // primary key column not found return new record
-                    $found = true;
-                    break;
-                }
-                $id[] = $this->_data[$fieldName];
-            }
-
-            if ($found) {
-                $recordName = $this->getComponentName();
-                $record = new $recordName($this, true);
-                $this->_data = array();
-                return $record;
-            }
-
-
-            $id = implode(' ', $id);
-
-            if (isset($this->_identityMap[$id])) {
-                $record = $this->_identityMap[$id];
-                $record->hydrate($this->_data);
-            } else {
-                $recordName = $this->getComponentName();
-                $record = new $recordName($this);
-                $this->_identityMap[$id] = $record;
-            }
-            $this->_data = array();
-        } else {
-            $recordName = $this->getComponentName();
-            $record = new $recordName($this, true);
-        }
-
-        return $record;
-    }
-
-    final public function applyInheritance($where)
-    {
-        if ( ! empty($this->_options['inheritanceMap'])) {
-            $a = array();
-            foreach ($this->_options['inheritanceMap'] as $field => $value) {
-                $a[] = $this->getColumnName($field) . ' = ?';
-            }
-            $i = implode(' AND ', $a);
-            $where .= ' AND ' . $i;
-        }
-        return $where;
-    }
-
-    public function count()
-    {
-        $a = $this->_conn->execute('SELECT COUNT(1) FROM ' . $this->_options['tableName'])->fetch(IPF_ORM::FETCH_NUM);
-        return current($a);
-    }
-
-    public function getQueryObject()
-    {
-        $graph = new IPF_ORM_Query($this->getConnection());
-        $graph->load($this->getComponentName());
-        return $graph;
-    }
-
-    public function getEnumValues($fieldName)
-    {
-        $columnName = $this->getColumnName($fieldName);
-        if (isset($this->_columns[$columnName]['values'])) {
-            return $this->_columns[$columnName]['values'];
-        } else {
-            return array();
-        }
-    }
-
-    public function enumValue($fieldName, $index)
-    {
-        if (IPF_ORM_Null::isNull($index)) {
-            return $index;
-        }
-
-        $columnName = $this->getColumnName($fieldName);
-        if ( ! $this->_conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)
-            && isset($this->_columns[$columnName]['values'][$index])
-        ) {
-            return $this->_columns[$columnName]['values'][$index];
-        }
-
-        return $index;
-    }
-
-    public function enumIndex($fieldName, $value)
-    {
-        $values = $this->getEnumValues($fieldName);
-
-        $index = array_search($value, $values);
-        if ($index === false || !$this->_conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
-            return $index;
-        }
-        return $value;
-    }
-
-    public function getColumnCount()
-    {
-        return count($this->_columns);
-    }
-
-    public function getColumns()
-    {
-        return $this->_columns;
-    }
-
-    public function removeColumn($fieldName)
-    {
-        if ($this->hasField($fieldName)) {
-            $columnName = $this->getColumnName($fieldName);
-            unset($this->_columnNames[$fieldName], $this->_fieldNames[$columnName], $this->_columns[$columnName]);
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    public function getColumnNames(array $fieldNames = null)
-    {
-        if ($fieldNames === null) {
-            return array_keys($this->_columns);
-        } else {
-           $columnNames = array();
-           foreach ($fieldNames as $fieldName) {
-               $columnNames[] = $this->getColumnName($fieldName);
-           }
-           return $columnNames;
-        }
-    }
-
-    public function getIdentifierColumnNames()
-    {
-        return $this->getColumnNames((array) $this->getIdentifier());
-    }
-
-    public function getFieldNames()
-    {
-        return array_values($this->_fieldNames);
-    }
-
-    public function getDefinitionOf($fieldName)
-    {
-        $columnName = $this->getColumnName($fieldName);
-        return $this->getColumnDefinition($columnName);
-    }
-
-    public function getTypeOf($fieldName)
-    {
-        return $this->getTypeOfColumn($this->getColumnName($fieldName));
-    }
-
-    public function getTypeOfColumn($columnName)
-    {
-        return isset($this->_columns[$columnName]) ? $this->_columns[$columnName]['type'] : false;
-    }
-
-    public function setData(array $data)
-    {
-        $this->_data = $data;
-    }
-
-    public function getData()
-    {
-        return $this->_data;
-    }
-
-    public function prepareValue($fieldName, $value, $typeHint = null)
-    {
-        if (IPF_ORM_Null::isNull($value)) {
-            return $value;
-        } else if ($value === null) {
-            return null;
-        } else {
-            $type = is_null($typeHint) ? $this->getTypeOf($fieldName) : $typeHint;
-
-            switch ($type) {
-                case 'integer':
-                case 'string';
-                    // don't do any casting here PHP INT_MAX is smaller than what the databases support
-                break;
-                case 'enum':
-                    return $this->enumValue($fieldName, $value);
-                break;
-                case 'boolean':
-                    return (boolean) $value;
-                break;
-                case 'array':
-                case 'object':
-                    if (is_string($value)) {
-                        $value = empty($value) ? null:unserialize($value);
-
-                        if ($value === false) {
-                            throw new IPF_ORM_Exception('Unserialization of ' . $fieldName . ' failed.');
-                        }
-                        return $value;
-                    }
-                break;
-                case 'gzip':
-                    $value = gzuncompress($value);
-
-                    if ($value === false) {
-                        throw new IPF_ORM_Exception('Uncompressing of ' . $fieldName . ' failed.');
-                    }
-                    return $value;
-                break;
-            }
-        }
-        return $value;
-    }
-
-    public function getComponentName()
-    {
-        return $this->_options['name'];
-    }
-
-    public function getTableName()
-    {
-        return $this->_options['tableName'];
-    }
-
-    public function setTableName($tableName)
-    {
-        $this->setOption('tableName', $tableName);
-    }
-
-    public function getTemplate($template)
-    {
-        if (!isset($this->_templates[$template]))
-            throw new IPF_ORM_Exception('Template ' . $template . ' not loaded');
-        return $this->_templates[$template];
-    }
-
-    public function hasTemplate($template)
-    {
-        return isset($this->_templates[$template]);
-    }
-
-    public function addTemplate($tpl, array $options=array())
-    {
-        if (!is_object($tpl)) {
-            $className = 'IPF_ORM_Template_' . $tpl;
-
-            if (class_exists($className, true)) {
-                $tpl = new $className($options);
-            } else if (class_exists($tpl, true)) {
-                $tpl = new $tpl($options);
-            } else {
-                throw new IPF_ORM_Record_Exception('Could not load behavior named: "' . $tpl . '"');
-            }
-        }
-
-        if (!($tpl instanceof IPF_ORM_Template)) {
-            throw new IPF_ORM_Record_Exception('Loaded behavior class is not an istance of IPF_ORM_Template.');
-        }
-
-        $className = get_class($tpl);
-        $this->_templates[$className] = $tpl;
-
-        $tpl->setTableDefinition($this);
-    }
-
-    public function setOrdering($ordering)
-    {
-        $this->_ordering = $ordering;
-    }
-
-    public function getOrdering()
-    {
-        return $this->_ordering;
-    }
-
-    public function bindQueryParts(array $queryParts)
-    {
-        $this->_options['queryParts'] = $queryParts;
-        return $this;
-    }
-
-    public function bindQueryPart($queryPart, $value)
-    {
-        $this->_options['queryParts'][$queryPart] = $value;
-        return $this;
-    }
-
-    public function getFieldValidators($fieldName)
-    {
-        $validators = array();
-        $columnName = $this->getColumnName($fieldName);
-        // this loop is a dirty workaround to get the validators filtered out of
-        // the options, since everything is squeezed together currently
-        
-        if (!isset($this->_columns[$columnName]))
-            return array();
-        
-        foreach ($this->_columns[$columnName] as $name => $args) {
-             if (empty($name)
-                    || $name == 'primary'
-                    || $name == 'protected'
-                    || $name == 'autoincrement'
-                    || $name == 'default'
-                    || $name == 'values'
-                    || $name == 'zerofill'
-                    || $name == 'owner'
-                    || $name == 'scale'
-                    || $name == 'type'
-                    || $name == 'length'
-                    || $name == 'fixed') {
-                continue;
-            }
-            if ($name == 'notnull' && isset($this->_columns[$columnName]['autoincrement'])) {
-                continue;
-            }
-            // skip it if it's explicitly set to FALSE (i.e. notnull => false)
-            if ($args === false) {
-                continue;
-            }
-            $validators[$name] = $args;
-        }
-
-        return $validators;
-    }
-
-    public function getBoundQueryPart($queryPart)
-    {
-        if (isset($this->_options['queryParts'][$queryPart]))
-            return $this->_options['queryParts'][$queryPart];
-        else
-            return null;
-    }
-
-    public function __toString()
-    {
-        return IPF_ORM_Utils::getTableAsString($this);
-    }
-
-    protected function findBy($fieldName, $value, $hydrationMode = null)
-    {
-        $q = $this->createQuery()->where($fieldName . ' = ?', array($value));
-        if ($this->_ordering)
-            $q->orderBy(implode(', ', $this->_ordering));
-        return $q->execute(array(), $hydrationMode);
-    }
-
-    protected function findOneBy($fieldName, $value, $hydrationMode = null)
-    {
-        $results = $this->createQuery()
-                        ->where($fieldName . ' = ?',array($value))
-                        ->limit(1)
-                        ->execute(array(), $hydrationMode);
-
-        if (is_array($results) && isset($results[0])) {
-            return $results[0];
-        } else if ($results instanceof IPF_ORM_Collection && $results->count() > 0) {
-            return $results->getFirst();
-        } else {
-            return false;
-        }
-    }
-
-    protected function _resolveFindByFieldName($name)
-    {
-        $fieldName = IPF_ORM_Inflector::tableize($name);
-        if ($this->hasColumn($name) || $this->hasField($name)) {
-            return $this->getFieldName($this->getColumnName($name));
-        } else if ($this->hasColumn($fieldName) || $this->hasField($fieldName)) {
-            return $this->getFieldName($this->getColumnName($fieldName));
-        } else {
-            return false;
-        }
-    }
-
-    public function __call($method, $arguments)
-    {
-        if (substr($method, 0, 6) == 'findBy') {
-            $by = substr($method, 6, strlen($method));
-            $method = 'findBy';
-        } else if (substr($method, 0, 9) == 'findOneBy') {
-            $by = substr($method, 9, strlen($method));
-            $method = 'findOneBy';
-        }
-
-        if (isset($by)) {
-            if ( ! isset($arguments[0])) {
-                throw new IPF_ORM_Exception('You must specify the value to findBy');
-            }
-
-            $fieldName = $this->_resolveFindByFieldName($by);
-            $hydrationMode = isset($arguments[1]) ? $arguments[1]:null;
-            if ($this->hasField($fieldName)) {
-                return $this->$method($fieldName, $arguments[0], $hydrationMode);
-            } else if ($this->hasRelation($by)) {
-                $relation = $this->getRelation($by);
-
-                if ($relation['type'] === IPF_ORM_Relation::MANY) {
-                    throw new IPF_ORM_Exception('Cannot findBy many relationship.');
-                }
-
-                return $this->$method($relation['local'], $arguments[0], $hydrationMode);
-            } else {
-                throw new IPF_ORM_Exception('Cannot find by: ' . $by . '. Invalid column or relationship alias.');
-            }
-        }
-
-        throw new IPF_ORM_Exception(sprintf('Unknown method %s::%s', get_class($this), $method));
-    }
-
-    public function notifyRecordListeners($method, $event)
-    {
-        foreach ($this->listeners as $listener)
-            if (is_callable(array($listener, $method)))
-                $listener->$method($event);
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/table/repository.php b/ipf/legacy_orm/orm/table/repository.php
deleted file mode 100644 (file)
index 1fd1557..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-
-class IPF_ORM_Table_Repository implements Countable, IteratorAggregate
-{
-    private $table;
-    private $registry = array();
-
-    public function __construct(IPF_ORM_Table $table)
-    {
-        $this->table = $table;
-    }
-
-    public function getTable()
-    {
-        return $this->table;
-    }
-
-    public function add(IPF_ORM_Record $record)
-    {
-        $oid = $record->getOID();
-
-        if (isset($this->registry[$oid])) {
-            return false;
-        }
-        $this->registry[$oid] = $record;
-
-        return true;
-    }
-
-    public function get($oid)
-    {
-        if ( ! isset($this->registry[$oid])) {
-            throw new IPF_ORM_Exception("Unknown object identifier");
-        }
-        return $this->registry[$oid];
-    }
-
-    public function count()
-    {
-        return count($this->registry);
-    }
-
-    public function evict($oid)
-    {
-        if ( ! isset($this->registry[$oid])) {
-            return false;
-        }
-        unset($this->registry[$oid]);
-        return true;
-    }
-
-    public function evictAll()
-    {
-        $evicted = 0;
-        foreach ($this->registry as $oid=>$record) {
-            if ($this->evict($oid)) {
-                $evicted++;
-            }
-        }
-        return $evicted;
-    }
-
-    public function getIterator()
-    {
-        return new ArrayIterator($this->registry);
-    }
-
-    public function contains($oid)
-    {
-        return isset($this->registry[$oid]);
-    }
-
-    public function loadAll()
-    {
-        $this->table->findAll();
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/template.php b/ipf/legacy_orm/orm/template.php
deleted file mode 100644 (file)
index 376a859..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-
-abstract class IPF_ORM_Template
-{
-    protected $_invoker;
-
-    public function setInvoker(IPF_ORM_Record $invoker)
-    {
-        $this->_invoker = $invoker;
-    }
-
-    public function getInvoker()
-    {
-        return $this->_invoker;
-    }
-
-    abstract public function setTableDefinition(IPF_ORM_Table $table);
-}
-
diff --git a/ipf/legacy_orm/orm/template/listener/orderable.php b/ipf/legacy_orm/orm/template/listener/orderable.php
deleted file mode 100644 (file)
index 157f492..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-class IPF_ORM_Template_Listener_Orderable
-{
-    private $columnName, $prepend;
-
-    public function __construct($columnName, $prepend)
-    {
-        $this->columnName = $columnName;
-        $this->prepend = $prepend;
-    }
-
-    public function preInsert(IPF_ORM_Event $event)
-    {
-        $this->setOrderValue($event->getInvoker());
-    }
-
-    public function preUpdate(IPF_ORM_Event $event)
-    {
-        $this->setOrderValue($event->getInvoker());
-    }
-
-    private function setOrderValue($obj)
-    {
-        $columnName = $this->columnName;
-        if ($obj->$columnName !== null)
-            return;
-
-        if ($this->prepend) {
-            $f = 'min';
-            $d = '-';
-        } else {
-            $f = 'max';
-            $d = '+';
-        }
-
-        $res = IPF_ORM_Query::create()
-            ->select('coalesce('.$f.'('.$this->columnName.') '.$d.' 1, 1) as x_ord')
-            ->from(get_class($obj))
-            ->execute();
-
-        $obj->$columnName = (int)$res[0]->x_ord;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/template/listener/owned.php b/ipf/legacy_orm/orm/template/listener/owned.php
deleted file mode 100644 (file)
index 7c99b69..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-
-class IPF_ORM_Template_Listener_Owned
-{
-    private $columnName;
-
-    public function __construct($columnName)
-    {
-        $this->columnName = $columnName;
-    }
-
-    public function preInsert(IPF_ORM_Event $event)
-    {
-        $this->setOwner($event->getInvoker());
-    }
-
-    public function preUpdate(IPF_ORM_Event $event)
-    {
-        $this->setOwner($event->getInvoker());
-    }
-
-    private function setOwner($obj)
-    {
-        $columnName = $this->columnName;
-        if ($obj->$columnName)
-            return;
-
-        $request = IPF_Project::getInstance()->request;
-        if ($request && !$request->user->isAnonymous()) {
-            $obj->$columnName = $request->user->id;
-        }
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/template/listener/sluggable.php b/ipf/legacy_orm/orm/template/listener/sluggable.php
deleted file mode 100644 (file)
index eaf0735..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-<?php
-
-class IPF_ORM_Template_Listener_Sluggable
-{
-    protected $_options = array();
-
-    public function __construct(array $options)
-    {
-        $this->_options = $options;
-    }
-
-    public function preInsert(IPF_ORM_Event $event)
-    {
-        $name = $this->_options['name'];
-
-        $record = $event->getInvoker();
-
-        if ( ! $record->$name) {
-            $record->$name = $this->buildSlug($record);
-        }
-    }
-
-    public function preUpdate(IPF_ORM_Event $event)
-    {
-        if (false !== $this->_options['unique']) {
-            $name = $this->_options['name'];
-    
-            $record = $event->getInvoker();
-
-            if ( ! $record->$name ||
-            (false !== $this->_options['canUpdate'] &&
-            array_key_exists($name, $record->getModified()))) {
-                $record->$name = $this->buildSlug($record);
-            }
-        }
-    }
-
-    protected function buildSlug($record)
-    {
-        if (empty($this->_options['fields'])) {
-            if (method_exists($record, 'getUniqueSlug')) {
-                $value = $record->getUniqueSlug($record);
-            } else {
-                $value = (string) $record;
-            }
-        } else {
-            if ($this->_options['unique'] === true) {   
-                $value = $this->getUniqueSlug($record);
-            } else {  
-                $value = '';
-                foreach ($this->_options['fields'] as $field) {
-                    $value .= $record->$field . ' ';
-                } 
-            }
-        }
-
-        $value =  call_user_func_array($this->_options['builder'], array($value, $record));
-
-        return $value;
-    }
-
-    public function getUniqueSlug($record)
-    {
-        $name = $this->_options['name'];
-        $slugFromFields = '';
-        foreach ($this->_options['fields'] as $field) {
-            $slugFromFields .= $record->$field . ' ';
-        }
-
-        $proposal = $record->$name ? $record->$name : $slugFromFields;
-        $proposal =  call_user_func_array($this->_options['builder'], array($proposal, $record));
-        $slug = $proposal;
-
-        $whereString = 'r.' . $name . ' LIKE ?';
-        $whereParams = array($proposal.'%');
-        
-        if ($record->exists()) {
-            $identifier = $record->identifier();
-            $whereString .= ' AND r.' . implode(' != ? AND r.', $record->getTable()->getIdentifierColumnNames()) . ' != ?';
-            $whereParams = array_merge($whereParams, array_values($identifier));
-        }
-
-        foreach ($this->_options['uniqueBy'] as $uniqueBy) {
-            if (is_null($record->$uniqueBy)) {
-                $whereString .= ' AND r.'.$uniqueBy.' IS NULL';
-            } else {
-                $whereString .= ' AND r.'.$uniqueBy.' = ?';
-                $whereParams[] =  $record->$uniqueBy;
-            }
-        }
-
-        $query = IPF_ORM_Query::create()
-        ->select('r.'.$name)
-        ->from(get_class($record).' r')
-        ->where($whereString , $whereParams)
-        ->setHydrationMode(IPF_ORM::HYDRATE_ARRAY);
-
-        $similarSlugResult = $query->execute();
-
-        $similarSlugs = array();
-        foreach ($similarSlugResult as $key => $value) {
-            $similarSlugs[$key] = $value[$name];
-        }
-
-        $i = 1;
-        while (in_array($slug, $similarSlugs)) {
-            $slug = $proposal.'-'.$i;
-            $i++;
-        }
-
-        return  $slug;
-    }
-}
diff --git a/ipf/legacy_orm/orm/template/listener/timestampable.php b/ipf/legacy_orm/orm/template/listener/timestampable.php
deleted file mode 100644 (file)
index 67afd8c..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
-
-class IPF_ORM_Template_Listener_Timestampable
-{
-    protected $_options = array();
-    public function __construct(array $options)
-    {
-        $this->_options = $options;
-    }
-
-    public function preInsert(IPF_ORM_Event $event)
-    {
-        if( ! $this->_options['created']['disabled']) {
-            $createdName = $this->_options['created']['name'];
-            $event->getInvoker()->$createdName = $this->getTimestamp('created');
-        }
-
-        if( ! $this->_options['updated']['disabled'] && $this->_options['updated']['onInsert']) {
-            $updatedName = $this->_options['updated']['name'];
-            $event->getInvoker()->$updatedName = $this->getTimestamp('updated');
-        }
-    }
-
-    public function preUpdate(IPF_ORM_Event $event)
-    {
-        if( ! $this->_options['updated']['disabled']) {
-            $updatedName = $this->_options['updated']['name'];
-            $event->getInvoker()->$updatedName = $this->getTimestamp('updated');
-        }
-    }
-
-    public function getTimestamp($type)
-    {
-        $options = $this->_options[$type];
-
-        if ($options['expression'] !== false && is_string($options['expression'])) {
-            return new IPF_ORM_Expression($options['expression']);
-        } else {
-            if ($options['type'] == 'date') {
-                return gmdate($options['format']);
-            } else if ($options['type'] == 'timestamp') {
-                return gmdate($options['format']);
-            } else {
-                return gmmktime();
-            }
-        }
-    }
-}
diff --git a/ipf/legacy_orm/orm/template/orderable.php b/ipf/legacy_orm/orm/template/orderable.php
deleted file mode 100644 (file)
index 4ed0a33..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-class IPF_ORM_Template_Orderable extends IPF_ORM_Template
-{
-    private $columnName = 'ord';
-    private $exclude = true;
-    private $prepend = false;
-
-    public function __construct(array $options=array())
-    {
-        if ($options) {
-            if (array_key_exists('name', $options))
-                $this->columnName = $options['name'];
-            if (array_key_exists('exclude', $options))
-                $this->exclude = $options['exclude'];
-            if (array_key_exists('prepend', $options))
-                $this->prepend = $options['prepend'];
-        }
-    }
-
-    public function getColumnName()
-    {
-        return $this->columnName;
-    }
-
-    public function setTableDefinition(IPF_ORM_Table $table)
-    {
-        $table->setColumn($this->columnName, 'integer', null, array('exclude' => $this->exclude));
-        $table->addIndex($table->getOption('tableName') . '_orderable_' . $this->columnName, array('fields' => array($this->columnName)));
-        $table->listeners['Orderable_'.$this->columnName] = new IPF_ORM_Template_Listener_Orderable($this->columnName, $this->prepend);
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/template/owned.php b/ipf/legacy_orm/orm/template/owned.php
deleted file mode 100644 (file)
index 8134201..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-
-class IPF_ORM_Template_Owned extends IPF_ORM_Template
-{
-    private $name = 'owner';
-    private $columnName = 'owner_id';
-    private $exclude = true;
-    private $verbose = 'owner';
-
-    public function __construct(array $options=array())
-    {
-        if ($options) {
-            if (array_key_exists('column', $options))
-                $this->columnName = $options['column'];
-            if (array_key_exists('name', $options))
-                $this->name = $options['name'];
-            if (array_key_exists('exclude', $options))
-                $this->exclude = $options['exclude'];
-            if (array_key_exists('verbose', $options))
-                $this->verbose = $options['verbose'];
-        }
-    }
-
-    public function getColumnName()
-    {
-        return $this->columnName;
-    }
-
-    public function setTableDefinition(IPF_ORM_Table $table)
-    {
-        $table->setColumn($this->columnName, 'integer', null, array(
-            'exclude'   => $this->exclude,
-            'verbose'   => $this->verbose,
-        ));
-
-        $fks = $table->getOption('foreignKeys', array());
-        $fks[] = array(
-            'local'        => $this->columnName,
-            'foreign'      => 'id',
-            'foreignTable' => 'auth_users',
-            'onUpdate'     => null,
-            'onDelete'     => 'CASCADE',
-        );
-        $table->setOption('foreignKeys', $fks);
-
-        $table->listeners['Owned_'.$this->columnName] = new IPF_ORM_Template_Listener_Owned($this->columnName);
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/template/sluggable.php b/ipf/legacy_orm/orm/template/sluggable.php
deleted file mode 100644 (file)
index 30d14ed..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-class IPF_ORM_Template_Sluggable extends IPF_ORM_Template
-{
-    protected $_options = array('name'          =>  'slug',
-                                'type'          =>  'string',
-                                'length'        =>  255,
-                                'unique'        =>  true,
-                                'options'       =>  array(),
-                                'fields'        =>  array(),
-                                'uniqueBy'      =>  array(),
-                                'uniqueIndex'   =>  true,
-                                'canUpdate'     =>  false,
-                                'builder'       =>  array('IPF_ORM_Inflector', 'urlize'),
-                                'indexName'     =>  'sluggable'
-    );
-
-    public function __construct(array $options = array())
-    {
-        $this->_options = IPF_ORM_Utils::arrayDeepMerge($this->_options, $options);
-    }
-
-    public function setTableDefinition(IPF_ORM_Table $table)
-    {
-        $table->setColumn($this->_options['name'], $this->_options['type'], $this->_options['length'], $this->_options['options']);
-
-        if ($this->_options['unique'] == true && $this->_options['uniqueIndex'] == true && !empty($this->_options['fields'])) {
-            $indexFields = array($this->_options['name']);
-            $indexFields = array_merge($indexFields, $this->_options['uniqueBy']);
-            $table->addIndex($this->_options['indexName'], array('fields' => $indexFields, 'type' => 'unique'));
-        }
-        $table->listeners['Sluggable_'.print_r($this->_options, true)] = new IPF_ORM_Template_Listener_Sluggable($this->_options);
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/template/timestampable.php b/ipf/legacy_orm/orm/template/timestampable.php
deleted file mode 100644 (file)
index 117b025..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-class IPF_ORM_Template_Timestampable extends IPF_ORM_Template
-{
-    protected $_options = array(
-        'created' => array(
-            'name'        => 'created_at',
-            'type'        => 'timestamp',
-            'format'      => 'Y-m-d H:i:s',
-            'disabled'    => false,
-            'expression'  => false,
-            'options'     => array('exclude' => true),
-        ),
-        'updated' => array(
-            'name'        => 'updated_at',
-            'type'        => 'timestamp',
-            'format'      => 'Y-m-d H:i:s',
-            'disabled'    => false,
-            'expression'  => false,
-            'onInsert'    => true,
-            'exclude'     => true,
-            'options'     => array('exclude' => true),
-        ),
-    );
-
-    public function __construct(array $options = array())
-    {
-        $this->_options = IPF_ORM_Utils::arrayDeepMerge($this->_options, $options);
-    }
-
-    public function setTableDefinition(IPF_ORM_Table $table)
-    {
-        if (!$this->_options['created']['disabled']) {
-            $table->setColumn($this->_options['created']['name'], $this->_options['created']['type'], null, $this->_options['created']['options']);
-        }
-        if (!$this->_options['updated']['disabled']) {
-            $table->setColumn($this->_options['updated']['name'], $this->_options['updated']['type'], null, $this->_options['updated']['options']);
-        }
-        $table->listeners['Timestampable_'.print_r($this->_options, true)] = new IPF_ORM_Template_Listener_Timestampable($this->_options);
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/transaction.php b/ipf/legacy_orm/orm/transaction.php
deleted file mode 100644 (file)
index cc60631..0000000
+++ /dev/null
@@ -1,267 +0,0 @@
-<?php
-
-class IPF_ORM_Transaction extends IPF_ORM_Connection_Module
-{
-    const STATE_SLEEP       = 0;
-    const STATE_ACTIVE      = 1;
-    const STATE_BUSY        = 2;
-
-    protected $_nestingLevel = 0;
-    protected $_internalNestingLevel = 0;
-    protected $invalid          = array();
-    protected $savePoints       = array();
-    protected $_collections     = array();
-
-    public function addCollection(IPF_ORM_Collection $coll)
-    {
-        $this->_collections[] = $coll;
-
-        return $this;
-    }
-
-    public function getState()
-    {
-        switch ($this->_nestingLevel) {
-            case 0:
-                return IPF_ORM_Transaction::STATE_SLEEP;
-                break;
-            case 1:
-                return IPF_ORM_Transaction::STATE_ACTIVE;
-                break;
-            default:
-                return IPF_ORM_Transaction::STATE_BUSY;
-        }
-    }
-
-    public function addInvalid(IPF_ORM_Record $record)
-    {
-        if (in_array($record, $this->invalid, true)) {
-            return false;
-        }
-        $this->invalid[] = $record;
-        return true;
-    }
-
-    public function getInvalid()
-    {
-        return $this->invalid;
-    }
-
-    public function getTransactionLevel()
-    {
-        return $this->_nestingLevel;
-    }
-    
-    public function getInternalTransactionLevel()
-    {
-        return $this->_internalNestingLevel;
-    }
-
-    public function beginTransaction($savepoint = null)
-    {
-        if ( ! is_null($savepoint)) {
-            $this->savePoints[] = $savepoint;
-
-            $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_CREATE);
-
-            $this->conn->notifyDBListeners('preSavepointCreate', $event);
-
-            if ( ! $event->skipOperation) {
-                $this->createSavePoint($savepoint);
-            }
-
-            $this->conn->notifyDBListeners('postSavepointCreate', $event);
-        } else {
-            if ($this->_nestingLevel == 0) {
-                $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_BEGIN);
-
-                $this->conn->notifyDBListeners('preTransactionBegin', $event);
-
-                if ( ! $event->skipOperation) {
-                    try {
-                        $this->_doBeginTransaction();
-                    } catch (Exception $e) {
-                        throw new IPF_ORM_Exception($e->getMessage());
-                    }
-                }
-                $this->conn->notifyDBListeners('postTransactionBegin', $event);
-            }
-        }
-
-        $level = ++$this->_nestingLevel;
-
-        return $level;
-    }
-
-    public function commit($savepoint = null)
-    {
-        if ($this->_nestingLevel == 0) {
-            throw new IPF_ORM_Exception("Commit failed. There is no active transaction.");
-        }
-
-        if ( ! is_null($savepoint)) {
-            $this->_nestingLevel -= $this->removeSavePoints($savepoint);
-
-            $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_COMMIT);
-
-            $this->conn->notifyDBListeners('preSavepointCommit', $event);
-
-            if ( ! $event->skipOperation) {
-                $this->releaseSavePoint($savepoint);
-            }
-
-            $this->conn->notifyDBListeners('postSavepointCommit', $event);
-        } else {
-                 
-            if ($this->_nestingLevel == 1 || $this->_internalNestingLevel == 1) {
-                if ( ! empty($this->invalid)) {
-                    if ($this->_internalNestingLevel == 1) {
-                        // transaction was started by IPF_ORM, so we are responsible
-                        // for a rollback
-                        $this->rollback();
-                        $tmp = $this->invalid;
-                        $this->invalid = array();
-                        throw new IPF_ORM_Exception_Validator($tmp);
-                    }
-                }
-                if ($this->_nestingLevel == 1) {
-                    // take snapshots of all collections used within this transaction
-                    foreach ($this->_collections as $coll) {
-                        $coll->takeSnapshot();
-                    }
-                    $this->_collections = array();
-
-                    $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_COMMIT);
-
-                    $this->conn->notifyDBListeners('preTransactionCommit', $event);
-                    if ( ! $event->skipOperation) {
-                        $this->_doCommit();
-                    }
-                    $this->conn->notifyDBListeners('postTransactionCommit', $event);
-                }
-            }
-            
-            if ($this->_nestingLevel > 0) {
-                $this->_nestingLevel--;
-            }            
-            if ($this->_internalNestingLevel > 0) {
-                $this->_internalNestingLevel--;
-            } 
-        }
-
-        return true;
-    }
-
-    public function rollback($savepoint = null)
-    {
-        if ($this->_nestingLevel == 0) {
-            throw new IPF_ORM_Exception("Rollback failed. There is no active transaction.");
-        }
-
-        if ($this->_internalNestingLevel > 1 || $this->_nestingLevel > 1) {
-            $this->_internalNestingLevel--;
-            $this->_nestingLevel--;
-            return false;
-        }
-
-        if ( ! is_null($savepoint)) {
-            $this->_nestingLevel -= $this->removeSavePoints($savepoint);
-
-            $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_ROLLBACK);
-
-            $this->conn->notifyDBListeners('preSavepointRollback', $event);
-            
-            if ( ! $event->skipOperation) {
-                $this->rollbackSavePoint($savepoint);
-            }
-
-            $this->conn->notifyDBListeners('postSavepointRollback', $event);
-        } else {
-            $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_ROLLBACK);
-    
-            $this->conn->notifyDBListeners('preTransactionRollback', $event);
-            
-            if ( ! $event->skipOperation) {
-                $this->_nestingLevel = 0;
-                $this->_internalNestingLevel = 0;
-                try {
-                    $this->_doRollback();
-                } catch (Exception $e) {
-                    throw new IPF_ORM_Exception($e->getMessage());
-                }
-            }
-
-            $this->conn->notifyDBListeners('postTransactionRollback', $event);
-        }
-
-        return true;
-    }
-
-    protected function createSavePoint($savepoint)
-    {
-        throw new IPF_ORM_Exception('Savepoints not supported by this driver.');
-    }
-
-    protected function releaseSavePoint($savepoint)
-    {
-        throw new IPF_ORM_Exception('Savepoints not supported by this driver.');
-    }
-
-    protected function rollbackSavePoint($savepoint)
-    {
-        throw new IPF_ORM_Exception('Savepoints not supported by this driver.');
-    }
-    
-    protected function _doRollback()
-    {
-        $this->conn->getDbh()->rollback();
-    }
-    
-    protected function _doCommit()
-    {
-        $this->conn->getDbh()->commit();
-    }
-    
-    protected function _doBeginTransaction()
-    {
-        $this->conn->getDbh()->beginTransaction();
-    }
-
-    private function removeSavePoints($savepoint)
-    {
-        $this->savePoints = array_values($this->savePoints);
-
-        $found = false;
-        $i = 0;
-
-        foreach ($this->savePoints as $key => $sp) {
-            if ( ! $found) {
-                if ($sp === $savepoint) {
-                    $found = true;
-                }
-            }
-            if ($found) {
-                $i++;
-                unset($this->savePoints[$key]);
-            }
-        }
-
-        return $i;
-    }
-
-    public function setIsolation($isolation)
-    {
-        throw new IPF_ORM_Exception('Transaction isolation levels not supported by this driver.');
-    }
-
-    public function getIsolation()
-    {
-        throw new IPF_ORM_Exception('Fetching transaction isolation level not supported by this driver.');
-    }
-    
-    public function beginInternalTransaction($savepoint = null)
-    {
-        $this->_internalNestingLevel++;
-        return $this->beginTransaction($savepoint);
-    }
-}
diff --git a/ipf/legacy_orm/orm/transaction/mysql.php b/ipf/legacy_orm/orm/transaction/mysql.php
deleted file mode 100644 (file)
index 20597ff..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-
-class IPF_ORM_Transaction_Mysql extends IPF_ORM_Transaction
-{
-    protected function createSavePoint($savepoint)
-    {
-        $query = 'SAVEPOINT ' . $savepoint;
-
-        return $this->conn->execute($query);
-    }
-
-    protected function releaseSavePoint($savepoint)
-    {
-        $query = 'RELEASE SAVEPOINT ' . $savepoint;
-
-        return $this->conn->execute($query);
-    }
-
-    protected function rollbackSavePoint($savepoint)
-    {
-        $query = 'ROLLBACK TO SAVEPOINT ' . $savepoint;
-
-        return $this->conn->execute($query);
-    }
-
-    public function setIsolation($isolation)
-    {
-        switch ($isolation) {
-            case 'READ UNCOMMITTED':
-            case 'READ COMMITTED':
-            case 'REPEATABLE READ':
-            case 'SERIALIZABLE':
-                break;
-            default:
-                throw new IPF_ORM_Exception('Isolation level ' . $isolation . ' is not supported.');
-        }
-
-        $query = 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $isolation;
-
-        return $this->conn->execute($query);
-    }
-
-    public function getIsolation()
-    {
-        return $this->conn->fetchOne('SELECT @@tx_isolation');
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/utils.php b/ipf/legacy_orm/orm/utils.php
deleted file mode 100644 (file)
index e6b55f2..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-<?php
-
-class IPF_ORM_Utils {
-
-    public static function getConnectionStateAsString($state)
-    {
-        switch ($state) {
-            case IPF_ORM_Transaction::STATE_SLEEP:
-                return "open";
-                break;
-            case IPF_ORM_Transaction::STATE_BUSY:
-                return "busy";
-                break;
-            case IPF_ORM_Transaction::STATE_ACTIVE:
-                return "active";
-                break;
-        }
-    }
-
-    public static function getConnectionAsString(IPF_ORM_Connection $connection)
-    {
-        $r[] = '<pre>';
-        $r[] = 'IPF_ORM_Connection object';
-        $r[] = 'State               : ' . IPF_ORM_Utils::getConnectionStateAsString($connection->transaction->getState());
-        $r[] = 'Open Transactions   : ' . $connection->transaction->getTransactionLevel();
-        $r[] = 'Table in memory     : ' . $connection->count();
-        $r[] = 'Driver name         : ' . $connection->getDriverName();
-        $r[] = "</pre>";
-        return implode("\n",$r)."<br>";
-    }
-    
-    public static function getValidators()
-    {
-        return array(
-            'country',
-            'creditcard',
-            'date',
-            'driver',
-            'email',
-            'exception',
-            'future',
-            'htmlcolor',
-            'ip',
-            'minlength',
-            'nospace',
-            'notblank',
-            'notnull',
-            'past',
-            'range',
-            'readonly',
-            'regexp',
-            'time',
-            'timestamp',
-            'unique',
-            'unsigned',
-            'usstate',
-            'file',
-            'image',
-            'html',
-            'uploadTo',
-            'verbose',
-        );
-    }
-
-    
-    public static function arrayDeepMerge()
-    {
-         switch (func_num_args()) {
-             case 0:
-                return false;
-             case 1:
-                return func_get_arg(0);
-             case 2:
-                $args = func_get_args();
-                $args[2] = array();
-                
-                if (is_array($args[0]) && is_array($args[1]))
-                {
-                    foreach (array_unique(array_merge(array_keys($args[0]),array_keys($args[1]))) as $key)
-                    {
-                        $isKey0 = array_key_exists($key, $args[0]);
-                        $isKey1 = array_key_exists($key, $args[1]);
-
-                        if ($isKey0 && $isKey1 && is_array($args[0][$key]) && is_array($args[1][$key]))
-                        {
-                            $args[2][$key] = self::arrayDeepMerge($args[0][$key], $args[1][$key]);
-                        } else if ($isKey0 && $isKey1) {
-                            $args[2][$key] = $args[1][$key];
-                        } else if ( ! $isKey1) {
-                            $args[2][$key] = $args[0][$key];
-                        } else if ( ! $isKey0) {
-                            $args[2][$key] = $args[1][$key];
-                        }
-                    }
-
-                    return $args[2];
-                } else {
-                    return $args[1];
-                }
-            default:
-                $args = func_get_args();
-                $args[1] = self::arrayDeepMerge($args[0], $args[1]);
-                array_shift($args);
-                return call_user_func_array(array('IPF', 'arrayDeepMerge'), $args);
-            break;
-        }
-    }        
-
-    public static function getTableAsString(IPF_ORM_Table $table)
-    {
-        $r[] = "<pre>";
-        $r[] = "Component   : ".$table->getComponentName();
-        $r[] = "Table       : ".$table->getTableName();
-        $r[] = "</pre>";
-        
-        return implode("\n",$r)."<br>";
-    }
-
-    public static function getCollectionAsString(IPF_ORM_Collection $collection)
-    {
-        $r[] = "<pre>";
-        $r[] = get_class($collection);
-        $r[] = 'data : ' . IPF_ORM::dump($collection->getData(), false);
-        //$r[] = 'snapshot : ' . IPF_ORM::dump($collection->getSnapshot());
-        $r[] = "</pre>";
-        return implode("\n",$r);
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/validator.php b/ipf/legacy_orm/orm/validator.php
deleted file mode 100644 (file)
index 104d089..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-<?php
-
-class IPF_ORM_Validator
-{
-    private static $validators = array();
-    public static function getValidator($name)
-    {
-        if ( ! isset(self::$validators[$name])) {
-            $class = 'IPF_ORM_Validator_' . ucfirst($name);
-            if (class_exists($class)) {
-                self::$validators[$name] = new $class;
-            } else if (class_exists($name)) {
-                self::$validators[$name] = new $name;
-            } else {
-                throw new IPF_ORM_Exception("Validator named '$name' not available.");
-            }
-
-        }
-        return self::$validators[$name];
-    }
-
-    public function validateRecord(IPF_ORM_Record $record)
-    {
-        $table = $record->getTable();
-
-        // if record is transient all fields will be validated
-        // if record is persistent only the modified fields will be validated
-        $fields = $record->exists() ? $record->getModified():$record->getData();
-        foreach ($fields as $fieldName => $value) {
-            $this->validateField($table, $fieldName, $value, $record);
-        }
-    }
-
-    private function validateField(IPF_ORM_Table $table, $fieldName, $value, IPF_ORM_Record $record)
-    {
-        if (IPF_ORM_Null::isNull($value)) {
-            $value = null;
-        } else if ($value instanceof IPF_ORM_Record) {
-            $value = $value->getIncremented();
-        }
-
-        $dataType = $table->getTypeOf($fieldName);
-
-        // Validate field type
-        if (!IPF_ORM_Validator::isValidType($value, $dataType)) {
-            $record->addError($fieldName, 'type');
-        }
-
-        if ($dataType == 'enum') {
-            $enumIndex = $table->enumIndex($fieldName, $value);
-            if ($enumIndex === false) {
-                $record->addError($fieldName, 'enum');
-            }
-        }
-
-        // Validate field length
-        $definition = $table->getDefinitionOf($fieldName);
-        if (!$this->validateLength($value, $dataType, $definition['length'])) {
-            $record->addError($fieldName, 'length');
-        }
-
-        // Run all custom validators
-        foreach ($table->getFieldValidators($fieldName) as $validatorName => $args) {
-            if (!is_string($validatorName)) {
-                $validatorName = $args;
-                $args = array();
-            }
-
-            $validator = IPF_ORM_Validator::getValidator($validatorName);
-            $validator->invoker = $record;
-            $validator->field = $fieldName;
-            $validator->args = $args;
-            if (!$validator->validate($value)) {
-                $record->addError($fieldName, $validatorName);
-            }
-        }
-    }
-
-    public function validateLength($value, $type, $maximumLength)
-    {
-        if ($type == 'timestamp' || $type == 'integer' || $type == 'enum') {
-            return true;
-        } else if ($type == 'array' || $type == 'object') {
-            $length = strlen(serialize($value));
-        } else if ($type == 'decimal') {
-            if (!$maximumLength)
-                $maximumLength = 18;
-            $length = strlen($value);
-        } else {
-            $length = strlen($value);
-        }
-
-        if ($length > $maximumLength) {
-            return false;
-        }
-        return true;
-    }
-
-    public function hasErrors()
-    {
-        return (count($this->stack) > 0);
-    }
-
-     public static function isValidType($var, $type)
-     {
-         if ($var instanceof IPF_ORM_Expression) {
-             return true;
-         } else if ($var === null) {
-             return true;
-         } else if (is_object($var)) {
-             return $type == 'object';
-         }
-
-         switch ($type) {
-             case 'float':
-             case 'double':
-             case 'decimal':
-                 return (string)$var == strval(floatval($var));
-             case 'integer':
-                 return (string)$var == strval(intval($var));
-             case 'string':
-                 return is_string($var) || is_numeric($var);
-             case 'blob':
-             case 'clob':
-             case 'gzip':
-                 return is_string($var);
-             case 'array':
-                 return is_array($var);
-             case 'object':
-                 return is_object($var);
-             case 'boolean':
-                 return is_bool($var) || (is_numeric($var) && ($var == 0 || $var == 1));
-             case 'timestamp':
-                 $validator = self::getValidator('timestamp');
-                 return $validator->validate($var);
-             case 'time':
-                 $validator = self::getValidator('time');
-                 return $validator->validate($var);
-             case 'date':
-                 $validator = self::getValidator('date');
-                 return $validator->validate($var);
-             case 'enum':
-                 return is_string($var) || is_int($var);
-             default:
-                 return false;
-         }
-     }
-}
-
diff --git a/ipf/legacy_orm/orm/validator/country.php b/ipf/legacy_orm/orm/validator/country.php
deleted file mode 100644 (file)
index 966ede8..0000000
+++ /dev/null
@@ -1,261 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Country
-{
-    private static $countries = array(
-        'ad' =>    'Andorra',
-        'ae' =>    'United Arab Emirates',
-        'af' =>    'Afghanistan',
-        'ag' =>    'Antigua and Barbuda',
-        'ai' =>    'Anguilla',
-        'al' =>    'Albania',
-        'am' =>    'Armenia',
-        'an' =>    'Netherlands Antilles',
-        'ao' =>    'Angola',
-        'aq' =>    'Antarctica',
-        'ar' =>    'Argentina',
-        'as' =>    'American Samoa',
-        'at' =>    'Austria',
-        'au' =>    'Australia',
-        'aw' =>    'Aruba',
-        'az' =>    'Azerbaijan',
-        'ba' =>    'Bosnia Hercegovina',
-        'bb' =>    'Barbados',
-        'bd' =>    'Bangladesh',
-        'be' =>    'Belgium',
-        'bf' =>    'Burkina Faso',
-        'bg' =>    'Bulgaria',
-        'bh' =>    'Bahrain',
-        'bi' =>    'Burundi',
-        'bj' =>    'Benin',
-        'bm' =>    'Bermuda',
-        'bn' =>    'Brunei Darussalam',
-        'bo' =>    'Bolivia',
-        'br' =>    'Brazil',
-        'bs' =>    'Bahamas',
-        'bt' =>    'Bhutan',
-        'bv' =>    'Bouvet Island',
-        'bw' =>    'Botswana',
-        'by' =>    'Belarus (Byelorussia)',
-        'bz' =>    'Belize',
-        'ca' =>    'Canada',
-        'cc' =>    'Cocos Islands',
-        'cd' =>    'Congo, The Democratic Republic of the',
-        'cf' =>    'Central African Republic',
-        'cg' =>    'Congo',
-        'ch' =>    'Switzerland',
-        'ci' =>    'Ivory Coast',
-        'ck' =>    'Cook Islands',
-        'cl' =>    'Chile',
-        'cm' =>    'Cameroon',
-        'cn' =>    'China',
-        'co' =>    'Colombia',
-        'cr' =>    'Costa Rica',
-        'cs' =>    'Czechoslovakia',
-        'cu' =>    'Cuba',
-        'cv' =>    'Cape Verde',
-        'cx' =>    'Christmas Island',
-        'cy' =>    'Cyprus',
-        'cz' =>    'Czech Republic',
-        'de' =>    'Germany',
-        'dj' =>    'Djibouti',
-        'dk' =>    'Denmark',
-        'dm' =>    'Dominica',
-        'do' =>    'Dominican Republic',
-        'dz' =>    'Algeria',
-        'ec' =>    'Ecuador',
-        'ee' =>    'Estonia',
-        'eg' =>    'Egypt',
-        'eh' =>    'Western Sahara',
-        'er' =>    'Eritrea',
-        'es' =>    'Spain',
-        'et' =>    'Ethiopia',
-        'fi' =>    'Finland',
-        'fj' =>    'Fiji',
-        'fk' =>    'Falkland Islands',
-        'fm' =>    'Micronesia',
-        'fo' =>    'Faroe Islands',
-        'fr' =>    'France',
-        'fx' =>    'France, Metropolitan FX',
-        'ga' =>    'Gabon',
-        'gb' =>    'United Kingdom (Great Britain)',
-        'gd' =>    'Grenada',
-        'ge' =>    'Georgia',
-        'gf' =>    'French Guiana',
-        'gh' =>    'Ghana',
-        'gi' =>    'Gibraltar',
-        'gl' =>    'Greenland',
-        'gm' =>    'Gambia',
-        'gn' =>    'Guinea',
-        'gp' =>    'Guadeloupe',
-        'gq' =>    'Equatorial Guinea',
-        'gr' =>    'Greece',
-        'gs' =>    'South Georgia and the South Sandwich Islands',
-        'gt' =>    'Guatemala',
-        'gu' =>    'Guam',
-        'gw' =>    'Guinea-bissau',
-        'gy' =>    'Guyana',
-        'hk' =>    'Hong Kong',
-        'hm' =>    'Heard and McDonald Islands',
-        'hn' =>    'Honduras',
-        'hr' =>    'Croatia',
-        'ht' =>    'Haiti',
-        'hu' =>    'Hungary',
-        'id' =>    'Indonesia',
-        'ie' =>    'Ireland',
-        'il' =>    'Israel',
-        'in' =>    'India',
-        'io' =>    'British Indian Ocean Territory',
-        'iq' =>    'Iraq',
-        'ir' =>    'Iran',
-        'is' =>    'Iceland',
-        'it' =>    'Italy',
-        'jm' =>    'Jamaica',
-        'jo' =>    'Jordan',
-        'jp' =>    'Japan',
-        'ke' =>    'Kenya',
-        'kg' =>    'Kyrgyzstan',
-        'kh' =>    'Cambodia',
-        'ki' =>    'Kiribati',
-        'km' =>    'Comoros',
-        'kn' =>    'Saint Kitts and Nevis',
-        'kp' =>    'North Korea',
-        'kr' =>    'South Korea',
-        'kw' =>    'Kuwait',
-        'ky' =>    'Cayman Islands',
-        'kz' =>    'Kazakhstan',
-        'la' =>    'Laos',
-        'lb' =>    'Lebanon',
-        'lc' =>    'Saint Lucia',
-        'li' =>    'Lichtenstein',
-        'lk' =>    'Sri Lanka',
-        'lr' =>    'Liberia',
-        'ls' =>    'Lesotho',
-        'lt' =>    'Lithuania',
-        'lu' =>    'Luxembourg',
-        'lv' =>    'Latvia',
-        'ly' =>    'Libya',
-        'ma' =>    'Morocco',
-        'mc' =>    'Monaco',
-        'md' =>    'Moldova Republic',
-        'mg' =>    'Madagascar',
-        'mh' =>    'Marshall Islands',
-        'mk' =>    'Macedonia, The Former Yugoslav Republic of',
-        'ml' =>    'Mali',
-        'mm' =>    'Myanmar',
-        'mn' =>    'Mongolia',
-        'mo' =>    'Macau',
-        'mp' =>    'Northern Mariana Islands',
-        'mq' =>    'Martinique',
-        'mr' =>    'Mauritania',
-        'ms' =>    'Montserrat',
-        'mt' =>    'Malta',
-        'mu' =>    'Mauritius',
-        'mv' =>    'Maldives',
-        'mw' =>    'Malawi',
-        'mx' =>    'Mexico',
-        'my' =>    'Malaysia',
-        'mz' =>    'Mozambique',
-        'na' =>    'Namibia',
-        'nc' =>    'New Caledonia',
-        'ne' =>    'Niger',
-        'nf' =>    'Norfolk Island',
-        'ng' =>    'Nigeria',
-        'ni' =>    'Nicaragua',
-        'nl' =>    'Netherlands',
-        'no' =>    'Norway',
-        'np' =>    'Nepal',
-        'nr' =>    'Nauru',
-        'nt' =>    'Neutral Zone',
-        'nu' =>    'Niue',
-        'nz' =>    'New Zealand',
-        'om' =>    'Oman',
-        'pa' =>    'Panama',
-        'pe' =>    'Peru',
-        'pf' =>    'French Polynesia',
-        'pg' =>    'Papua New Guinea',
-        'ph' =>    'Philippines',
-        'pk' =>    'Pakistan',
-        'pl' =>    'Poland',
-        'pm' =>    'St. Pierre and Miquelon',
-        'pn' =>    'Pitcairn',
-        'pr' =>    'Puerto Rico',
-        'pt' =>    'Portugal',
-        'pw' =>    'Palau',
-        'py' =>    'Paraguay',
-        'qa' =>    'Qatar',
-        're' =>    'Reunion',
-        'ro' =>    'Romania',
-        'ru' =>    'Russia',
-        'rw' =>    'Rwanda',
-        'sa' =>    'Saudi Arabia',
-        'sb' =>    'Solomon Islands',
-        'sc' =>    'Seychelles',
-        'sd' =>    'Sudan',
-        'se' =>    'Sweden',
-        'sg' =>    'Singapore',
-        'sh' =>    'St. Helena',
-        'si' =>    'Slovenia',
-        'sj' =>    'Svalbard and Jan Mayen Islands',
-        'sk' =>    'Slovakia (Slovak Republic)',
-        'sl' =>    'Sierra Leone',
-        'sm' =>    'San Marino',
-        'sn' =>    'Senegal',
-        'so' =>    'Somalia',
-        'sr' =>    'Suriname',
-        'st' =>    'Sao Tome and Principe',
-        'sv' =>    'El Salvador',
-        'sy' =>    'Syria',
-        'sz' =>    'Swaziland',
-        'tc' =>    'Turks and Caicos Islands',
-        'td' =>    'Chad',
-        'tf' =>    'French Southern Territories',
-        'tg' =>    'Togo',
-        'th' =>    'Thailand',
-        'tj' =>    'Tajikistan',
-        'tk' =>    'Tokelau',
-        'tm' =>    'Turkmenistan',
-        'tn' =>    'Tunisia',
-        'to' =>    'Tonga',
-        'tp' =>    'East Timor',
-        'tr' =>    'Turkey',
-        'tt' =>    'Trinidad, Tobago',
-        'tv' =>    'Tuvalu',
-        'tw' =>    'Taiwan',
-        'tz' =>    'Tanzania',
-        'ua' =>    'Ukraine',
-        'ug' =>    'Uganda',
-        'uk' =>    'United Kingdom',
-        'um' =>    'United States Minor Islands',
-        'us' =>    'United States of America',
-        'uy' =>    'Uruguay',
-        'uz' =>    'Uzbekistan',
-        'va' =>    'Vatican City',
-        'vc' =>    'Saint Vincent, Grenadines',
-        've' =>    'Venezuela',
-        'vg' =>    'Virgin Islands (British)',
-        'vi' =>    'Virgin Islands (USA)',
-        'vn' =>    'Viet Nam',
-        'vu' =>    'Vanuatu',
-        'wf' =>    'Wallis and Futuna Islands',
-        'ws' =>    'Samoa',
-        'ye' =>    'Yemen',
-        'yt' =>    'Mayotte',
-        'yu' =>    'Yugoslavia',
-        'za' =>    'South Africa',
-        'zm' =>    'Zambia',
-        'zr' =>    'Zaire',
-        'zw' =>    'Zimbabwe');
-
-    public static function getCountries()
-    {
-        return self::$countries;
-    }
-
-    public function validate($value)
-    {
-        $value = strtolower($value);
-        return isset(self::$countries[$value]);
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/validator/creditcard.php b/ipf/legacy_orm/orm/validator/creditcard.php
deleted file mode 100644 (file)
index 62609ac..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Creditcard
-{                                                         
-    public function validate($value)
-    {
-        $card_regexes = array(
-            "/^4\d{12}(\d\d\d){0,1}$/"      => 'visa',
-            "/^5[12345]\d{14}$/"            => 'mastercard',
-            "/^3[47]\d{13}$/"               => 'amex',
-            "/^6011\d{12}$/"                => 'discover',
-            "/^30[012345]\d{11}$/"          => 'diners',
-            "/^3[68]\d{12}$/"               => 'diners',
-        );
-
-        $cardType = '';
-        foreach ($card_regexes as $regex => $type) {
-            if (preg_match($regex, $value)) {
-                 $cardType = $type;
-                 break;
-            }
-        }
-        if (!$cardType)
-            return false;
-
-        /* mod 10 checksum algorithm */
-        $revcode = strrev($value);
-        $checksum = 0;
-        for ($i = 0; $i < strlen($revcode); $i++) {
-            $currentNum = intval($revcode[$i]);
-            if ($i & 1) {               /* Odd position */
-                 $currentNum *= 2;
-            }
-            /* Split digits and add. */
-            $checksum += $currentNum % 10;
-            if ($currentNum > 9) {
-                 $checksum += 1;
-            }
-        }
-        return $checksum % 10 == 0;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/validator/date.php b/ipf/legacy_orm/orm/validator/date.php
deleted file mode 100644 (file)
index b0916a9..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Date
-{
-    public function validate($value)
-    {
-        if ($value === null) {
-            return true;
-        }
-        $e = explode('-', $value);
-
-        if (count($e) !== 3) {
-            return false;
-        }
-        return checkdate($e[1], $e[2], $e[0]);
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/driver.php b/ipf/legacy_orm/orm/validator/driver.php
deleted file mode 100644 (file)
index 324d9da..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Driver
-{
-    protected $_args = array();
-
-    public function __get($arg)
-    {
-        if (isset($this->_args[$arg])) {
-            return $this->_args[$arg];
-        }
-        return null;
-    }
-
-    public function __isset($arg)
-    {
-        return isset($this->_args[$arg]);
-    }
-
-    public function __set($arg, $value)
-    {
-        $this->_args[$arg] = $value;
-        
-        return $this;
-    }
-
-    public function getArg($arg)
-    {
-        if ( ! isset($this->_args[$arg])) {
-            throw new IPF_ORM_Exception_Validator('Unknown option ' . $arg);
-        }
-        return $this->_args[$arg];
-    }
-
-    public function setArg($arg, $value)
-    {
-        $this->_args[$arg] = $value;
-        
-        return $this;
-    }
-
-    public function getArgs()
-    {
-        return $this->_args;
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/email.php b/ipf/legacy_orm/orm/validator/email.php
deleted file mode 100644 (file)
index 6e07505..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Email
-{
-    public function validate($value)
-    {
-        if ($value === null)
-            return true;
-        return IPF_Utils::isEmail($value);
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/exclude.php b/ipf/legacy_orm/orm/validator/exclude.php
deleted file mode 100644 (file)
index c97ea11..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Exclude
-{
-    public function validate($value)
-    {
-        return true;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/validator/file.php b/ipf/legacy_orm/orm/validator/file.php
deleted file mode 100644 (file)
index b235643..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_File
-{
-    public function validate($value){
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/future.php b/ipf/legacy_orm/orm/validator/future.php
deleted file mode 100644 (file)
index b075f38..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Future
-{
-    public function validate($value)
-    {
-        if ($value === null) {
-            return true;
-        }
-        $e = explode('-', $value);
-
-        if (count($e) !== 3) {
-            return false;
-        }
-        
-        if (is_array($this->args) && isset($this->args['timezone'])) {
-            switch (strtolower($this->args['timezone'])) {
-                case 'gmt':
-                    $now = gmdate("U") - date("Z");
-                    break;
-                default:
-                    $now = getdate();
-                    break;
-            }
-        } else {
-            $now = getdate();
-        }
-        
-        if ($now['year'] > $e[0]) {
-            return false;
-        } else if ($now['year'] == $e[0]) {
-            if ($now['mon'] > $e[1]) {
-                return false;
-            } else if ($now['mon'] == $e[1]) {
-                return $now['mday'] < $e[2];
-            } else {
-                return true;
-            }
-        } else {
-            return true;
-        }
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/html.php b/ipf/legacy_orm/orm/validator/html.php
deleted file mode 100644 (file)
index bd663d0..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Html
-{
-    public function validate($value){
-        return true;
-    }
-}
diff --git a/ipf/legacy_orm/orm/validator/htmlcolor.php b/ipf/legacy_orm/orm/validator/htmlcolor.php
deleted file mode 100644 (file)
index 59cf78d..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Htmlcolor
-{
-    public function validate($value)
-    {
-        if ( ! preg_match("/^#{0,1}[0-9a-fA-F]{6}$/", $value)) {
-            return false;
-        }
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/image.php b/ipf/legacy_orm/orm/validator/image.php
deleted file mode 100644 (file)
index fb2e1e8..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Image
-{
-    public function validate($value){
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/ip.php b/ipf/legacy_orm/orm/validator/ip.php
deleted file mode 100644 (file)
index 8642f32..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Ip
-{
-    public function validate($value)
-    {
-        return (bool) ip2long(str_replace("\0", '', $value));
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/minlength.php b/ipf/legacy_orm/orm/validator/minlength.php
deleted file mode 100644 (file)
index 35583bd..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Minlength 
-{
-    public function validate($value)
-    {
-        if (isset($this->args) && strlen($value) < $this->args) {
-            return false;
-        }
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/nospace.php b/ipf/legacy_orm/orm/validator/nospace.php
deleted file mode 100644 (file)
index b9f326c..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Nospace extends IPF_ORM_Validator_Driver
-{
-    public function validate($value)
-    {
-        return ($value === null || ! preg_match('/\s/', $value));
-    }
-}
diff --git a/ipf/legacy_orm/orm/validator/notblank.php b/ipf/legacy_orm/orm/validator/notblank.php
deleted file mode 100644 (file)
index c3fcc81..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Notblank extends IPF_ORM_Validator_Driver
-{
-    public function validate($value)
-    {
-        return (trim($value) !== '' && $value !== null);
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/notnull.php b/ipf/legacy_orm/orm/validator/notnull.php
deleted file mode 100644 (file)
index a7ca31e..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Notnull extends IPF_ORM_Validator_Driver
-{
-    public function validate($value)
-    {
-        return ($value !== null);
-    }
-}
diff --git a/ipf/legacy_orm/orm/validator/past.php b/ipf/legacy_orm/orm/validator/past.php
deleted file mode 100644 (file)
index b620013..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Past
-{
-    public function validate($value)
-    {
-        if ($value === null) {
-            return true;
-        }
-        $e = explode('-', $value);
-
-        if (count($e) !== 3) {
-            return false;
-        }
-        
-        if (is_array($this->args) && isset($this->args['timezone'])) {
-            switch (strtolower($this->args['timezone'])) {
-                case 'gmt':
-                    $now = gmdate("U") - date("Z");
-                    break;
-                default:
-                    $now = getdate();
-                    break;
-            }
-        } else {
-            $now = getdate();
-        }
-        
-        if ($now['year'] < $e[0]) {
-            return false;
-        } else if ($now['year'] == $e[0]) {
-            if ($now['mon'] < $e[1]) {
-                return false;
-            } else if ($now['mon'] == $e[1]) {
-                return $now['mday'] > $e[2];
-            } else {
-                return true;
-            }
-        } else {
-            return true;
-        }
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/range.php b/ipf/legacy_orm/orm/validator/range.php
deleted file mode 100644 (file)
index 2a895d3..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Range
-{
-    public function validate($value)
-    {
-        if (isset($this->args[0]) && $value < $this->args[0]) {
-            return false;
-        }
-        if (isset($this->args[1]) && $value > $this->args[1]) {
-            return false;
-        }
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/readonly.php b/ipf/legacy_orm/orm/validator/readonly.php
deleted file mode 100644 (file)
index dac58f8..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Readonly
-{
-    public function validate($value)
-    {
-        $modified = $this->invoker->getModified();
-        return array_key_exists($this->field, $modified) ? false : true;
-    }
-}
diff --git a/ipf/legacy_orm/orm/validator/regexp.php b/ipf/legacy_orm/orm/validator/regexp.php
deleted file mode 100644 (file)
index 3981f13..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Regexp
-{
-    public function validate($value)
-    {
-        if ( ! isset($this->args)) {
-           return true;
-        }
-        if (is_array($this->args)) {
-            foreach ($this->args as $regexp) {
-                if ( ! preg_match($regexp, $value)) {
-                    return false;
-                }
-            }
-            return true;
-        } else {
-            if (preg_match($this->args, $value)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/time.php b/ipf/legacy_orm/orm/validator/time.php
deleted file mode 100644 (file)
index 7c90fd4..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Time
-{
-    public function validate($value)
-    {
-        if ($value === null) {
-            return true;
-        }
-
-        $e = explode(':', $value);
-
-        if (count($e) !== 3) {
-            return false;
-        }
-
-        if ( ! preg_match('/^ *[0-9]{2}:[0-9]{2}:[0-9]{2} *$/', $value)) {
-            return false;
-        }
-
-        $hr = intval($e[0], 10);
-        $min = intval($e[1], 10);
-        $sec = intval($e[2], 10);
-
-        return $hr >= 0 && $hr <= 23 && $min >= 0 && $min <= 59 && $sec >= 0 && $sec <= 59;      
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/timestamp.php b/ipf/legacy_orm/orm/validator/timestamp.php
deleted file mode 100644 (file)
index ab95161..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Timestamp
-{
-    public function validate($value)
-    {
-        if ($value === null) {
-            return true;
-        }
-
-        if ( ! preg_match('/^ *[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} *$/', $value)) {
-            return false;
-        }
-
-        list($date, $time) = explode(' ', trim($value));
-
-        $dateValidator = IPF_ORM_Validator::getValidator('date');
-        $timeValidator = IPF_ORM_Validator::getValidator('time');
-
-        if ( ! $dateValidator->validate($date)) {
-            return false;
-        }
-
-        if ( ! $timeValidator->validate($time)) {
-            return false;
-        }
-
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/unique.php b/ipf/legacy_orm/orm/validator/unique.php
deleted file mode 100644 (file)
index 8f03404..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Unique
-{
-    public function validate($value)
-    {
-        $table = $this->invoker->getTable();
-        $pks = $table->getIdentifier();
-
-        if ( is_array($pks) ) {
-            $pks = join(',', $pks);
-        }
-
-        $sql   = 'SELECT ' . $pks . ' FROM ' . $table->getTableName() . ' WHERE ' . $this->field . ' = ?';
-        
-        $values = array();
-        $values[] = $value;
-        
-        // If the record is not new we need to add primary key checks because its ok if the 
-        // unique value already exists in the database IF the record in the database is the same
-        // as the one that is validated here.
-        $state = $this->invoker->state();
-        if ( ! ($state == IPF_ORM_Record::STATE_TDIRTY || $state == IPF_ORM_Record::STATE_TCLEAN)) {
-            foreach ((array) $table->getIdentifier() as $pk) {
-                $sql .= " AND {$pk} != ?";
-                $values[] = $this->invoker->$pk;
-            }
-        }
-        
-        $stmt  = $table->getConnection()->getDbh()->prepare($sql);
-        $stmt->execute($values);
-
-        return ( ! is_array($stmt->fetch()));
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/unsigned.php b/ipf/legacy_orm/orm/validator/unsigned.php
deleted file mode 100644 (file)
index 7f6cf1e..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Unsigned
-{
-    public function validate($value)
-    {
-        $int = (int) $value;
-
-        if ($int != $value || $int < 0) {
-            return false;
-        }
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/uploadto.php b/ipf/legacy_orm/orm/validator/uploadto.php
deleted file mode 100644 (file)
index 2a99a68..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_UploadTo
-{
-    public function validate($value)
-    {
-        return true;
-    }
-}
-
diff --git a/ipf/legacy_orm/orm/validator/usstate.php b/ipf/legacy_orm/orm/validator/usstate.php
deleted file mode 100644 (file)
index b76a1b1..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Usstate
-{
-    private static $states = array(
-                'AK' => true,
-                'AL' => true,
-                'AR' => true,
-                'AZ' => true,
-                'CA' => true,
-                'CO' => true,
-                'CT' => true,
-                'DC' => true,
-                'DE' => true,
-                'FL' => true,
-                'GA' => true,
-                'HI' => true,
-                'IA' => true,
-                'ID' => true,
-                'IL' => true,
-                'IN' => true,
-                'KS' => true,
-                'KY' => true,
-                'LA' => true,
-                'MA' => true,
-                'MD' => true,
-                'ME' => true,
-                'MI' => true,
-                'MN' => true,
-                'MO' => true,
-                'MS' => true,
-                'MT' => true,
-                'NC' => true,
-                'ND' => true,
-                'NE' => true,
-                'NH' => true,
-                'NJ' => true,
-                'NM' => true,
-                'NV' => true,
-                'NY' => true,
-                'OH' => true,
-                'OK' => true,
-                'OR' => true,
-                'PA' => true,
-                'PR' => true,
-                'RI' => true,
-                'SC' => true,
-                'SD' => true,
-                'TN' => true,
-                'TX' => true,
-                'UT' => true,
-                'VA' => true,
-                'VI' => true,
-                'VT' => true,
-                'WA' => true,
-                'WI' => true,
-                'WV' => true,
-                'WY' => true
-            );
-    public function getStates()
-    {
-        return self::$states;
-    }
-
-    public function validate($value)
-    {
-        return isset(self::$states[$value]);
-    }
-}
\ No newline at end of file
diff --git a/ipf/legacy_orm/orm/validator/verbose.php b/ipf/legacy_orm/orm/validator/verbose.php
deleted file mode 100644 (file)
index 0a6478e..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-class IPF_ORM_Validator_Verbose
-{
-    public function validate($value)
-    {
-        return true;
-    }
-}
-
diff --git a/src/adminmodel.php b/src/adminmodel.php
new file mode 100644 (file)
index 0000000..75d0b94
--- /dev/null
@@ -0,0 +1,613 @@
+<?php
+
+use \PFF\HtmlBuilder\Text as Text;
+use \PFF\HtmlBuilder\Tag as Tag;
+
+class IPF_Admin_Model extends IPF_Admin_Component
+{
+    public $modelName;
+
+    public function __construct($modelName)
+    {
+        $this->modelName = $modelName;
+    }
+
+    public function verbose_name()
+    {
+        return IPF_Utils::humanTitle($this->modelName);
+    }
+
+    public function slug()
+    {
+        return strtolower($this->modelName);
+    }
+
+    private function table()
+    {
+        return IPF_ORM::getTable($this->modelName);
+    }
+
+    protected function query($searchValue, $filters)
+    {
+        if (method_exists($this->modelName, 'ordering')) {
+            $m = new $this->modelName;
+            $ord = $m->ordering();
+        } elseif ($this->table()->getOrdering()) {
+            $ord = implode(', ', $this->table()->getOrdering());
+        } elseif ($this->table()->hasTemplate('IPF_ORM_Template_Orderable')) {
+            $ord = $this->table()->getTemplate('IPF_ORM_Template_Orderable')->getColumnName();
+        } else {
+            $ord = '1 desc';
+        }
+
+        $q = IPF_ORM_Query::create()->from($this->modelName)->orderby($ord);
+
+        if ($searchValue) {
+            $wh = array();
+            $whv = array();
+            foreach ($this->_searchFields() as $f) {
+                $wh[] = $f.' like ?';
+                $whv[] = '%'.$searchValue.'%';
+            }
+            $q->addWhere($wh, $whv);
+        }
+
+        foreach ($filters as $f)
+            $f->applyToQuery($q);
+
+        return $q;
+    }
+
+    protected function getItems($searchValue, $filters, $page, $pageSize)
+    {
+        $idColumn = $this->table()->getIdentifier();
+
+        $result = array();
+        foreach ($this->query($searchValue, $filters)->limit($pageSize)->offset(($page - 1) * $pageSize)->execute() as $o) {
+            $id = $o->__get($idColumn);
+            $result[$id] = $o;
+        }
+        return $result;
+    }
+
+    protected function itemsCount($searchValue, $filters)
+    {
+        return $this->query($searchValue, $filters)->count();
+    }
+
+    public function getObjectByID($id)
+    {
+        return $this->table()->find($id);
+    }
+
+    public function searcheable()
+    {
+        return count($this->_searchFields()) > 0;
+    }
+
+    protected function _searchFields()
+    {
+        return array();
+    }
+
+    public function list_display()
+    {
+        return $this->table()->getColumnNames();
+    }
+
+    public function renderCell($object, $column)
+    {
+        $value = $object->$column;
+        switch ($object->getTable()->getTypeOf($column)) {
+            case 'boolean':
+                return $value
+                    ? '<span class="positive">&#x2714;</span>'
+                    : '<span class="negative">&#x2718;</span>';
+            case 'timestamp':
+                return Text::escape(IPF_Utils::formatDate($value));
+            default:
+                return Text::escape($value);
+        }
+    }
+
+    protected function columnTitle($column)
+    {
+        $columns = $this->table()->getColumns();
+        if (array_key_exists($column, $columns) && array_key_exists('verbose', $columns[$column]))
+            return $columns[$column]['verbose'];
+        else
+            return parent::columnTitle($column);
+    }
+
+    public function _orderable()
+    {
+        return $this->_orderableColumn() !== null;
+    }
+
+    public function _orderableColumn()
+    {
+        if (method_exists($this, 'list_order'))
+            return $this->list_order();
+        elseif ($this->table()->hasTemplate('IPF_ORM_Template_Orderable'))
+            return $this->table()->getTemplate('IPF_ORM_Template_Orderable')->getColumnName();
+        else
+            return null;
+    }
+
+    /* filters */
+
+    protected function _listFilters()
+    {
+        return array();
+    }
+
+    public function listFilters()
+    {
+        $filters = array();
+        foreach ($this->_listFilters() as $f) {
+            if (is_string($f))
+                $f = new IPF_Admin_Model_RelationFilter($this->modelName, $f);
+
+            $filters[] = $f;
+        }
+        return $filters;
+    }
+
+    public function reorder($ids)
+    {
+        if (!$this->_orderable())
+            return false;
+
+        $ord_field = $this->_orderableColumn();
+
+        $table = IPF_ORM::getTable($this->modelName);
+        $conn = $table->getConnection();
+
+        $idColumn = $table->getIdentifier();
+        if (is_array($idColumn))
+            $idColumn = $idColumn[0];
+
+        $questions = str_repeat('?,', count($ids)-1) . '?';
+        $query = 'SELECT ' . $conn->quoteIdentifier($ord_field) .
+            ' FROM ' . $conn->quoteIdentifier($table->getTableName()) .
+            ' WHERE ' . $conn->quoteIdentifier($idColumn) . ' IN (' . $questions . ')' .
+            ' ORDER BY ' . $conn->quoteIdentifier($ord_field);
+        $ords = $conn->fetchColumn($query, $ids);
+
+        $i = 0;
+        foreach ($ids as $id) {
+            $item = $table->find($id);
+            $item[$ord_field] = $ords[$i];
+            $item->save();
+            $i++;
+        }
+
+        return true;
+    }
+
+    /* edit */
+
+    protected function _getForm($model, $data)
+    {
+        $extra = array(
+            'user_fields' => $this->fields(),
+            'inlines' => $this->inlines(),
+        );
+        return new IPF_Admin_ModelForm($data, $model, $this->modelName, $extra);
+    }
+
+    public function inlines()
+    {
+        return array();
+    }
+
+    public function saveObject($form, $model)
+    {
+        $model = $form->save();
+        $idColumn = $this->table()->getIdentifier();
+        return array($model->$idColumn, $model);
+    }
+
+    public function deleteObject($model)
+    {
+        $model->delete();
+    } 
+
+    public function fields()
+    {
+        return null;
+    }
+}
+
+class IPF_Admin_ModelForm extends IPF_Form_Model
+{
+    private $inlines = array();
+
+    function __construct($data=null, $model=null, $modelClass='', $extra=array())
+    {
+        $editMode = $model !== null;
+
+        if (!$model)
+            $model = new $modelClass;
+
+        $extra['model'] = $model;
+
+        if ($extra['inlines']) {
+            foreach ($extra['inlines'] as $inlineClassName) {
+                $this->inlines[] = new $inlineClassName($model);
+            }
+        }
+
+        if ($editMode) {
+            $extra['initial'] = $this->getFormData($model, $this->inlines);
+        }
+
+        parent::__construct($data, $extra);
+    }
+
+    function initFields($extra=array())
+    {
+        parent::initFields($extra);
+
+        if ($this->inlines) {
+            $this->field_groups[] = array('fields' => array_keys($this->fields));
+        }
+
+        foreach ($this->inlines as $inline) {
+            $name = $inline->getModelName();
+            $this->fields[$name] = $inline->createField();
+
+            $this->field_groups[] = array(
+                'label' => $inline->getLegend(),
+                'fields' => array($name),
+            );
+        }
+    }
+
+    private function getFormData($o, $inlines)
+    {
+        $data = $o->getData();
+        foreach ($o->getTable()->getRelations() as $rname => $rel) {
+            $fields = $this->fields();
+            if (!$fields || in_array($rname, $fields)) {
+                if ($rel->getType() == IPF_ORM_Relation::MANY_AGGREGATE) {
+                    $data[$rname] = array();
+                    foreach ($rel->fetchRelatedFor($o) as $ri) {
+                        $data[$rname][] = $ri->pk();
+                    }
+                }
+            }
+        }
+
+        foreach ($inlines as $inline) {
+            $objs = array();
+            foreach ($inline->getObjects() as $io) {
+                $d = $io->getData();
+                $d['id'] = $io->pk();
+                $objs[] = $d;
+            }
+            $data[$inline->getModelName()] = $objs;
+        }
+
+        return $data;
+    }
+
+    function save($commit=true)
+    {
+        $model = parent::save($commit);
+
+        foreach ($this->inlines as $inline) {
+            $this->saveInlineObject($inline, $model);
+        }
+
+        return $model;
+    }
+
+    private function saveInlineObject($inline, $model)
+    {
+        $objIndex = array();
+        foreach ($inline->getObjects() as $obj)
+            $objIndex[$obj->pk()] = $obj;
+
+        $modelName = $inline->getModelName();
+        $fk_local = $inline->getFkLocal();
+
+        foreach ($this->cleaned_data[$modelName] as $objData) {
+            if (array_key_exists('id', $objData) && array_key_exists($objData['id'], $objIndex)) {
+                $obj = $objIndex[$objData['id']];
+                if (isset($objData['is_remove'])) {
+                    $obj->delete();
+                } else {
+                    $obj->synchronizeWithArray($objData);
+                    $obj->save();
+                }
+            } else {
+                $objData[$fk_local] = $model->id;
+                $obj = new $modelName;
+                $obj->synchronizeWithArray($objData);
+                $obj->save();
+            }
+        }
+    }
+}
+
+abstract class IPF_Admin_Model_Filter implements IPF_Admin_ListFilter
+{
+    abstract function applyToQuery($q);
+
+    protected $title, $paramName;
+
+    function title()
+    {
+        return $this->title;
+    }
+
+    protected function link($params, $value, $title)
+    {
+        if ($value !== null)
+            $params[$this->paramName] = $value;
+        else
+            unset($params[$this->paramName]);
+
+        return Tag::a()
+            ->attr('href', '?'.IPF_HTTP_URL::generateParams($params, false))
+            ->append($title);
+    }
+}
+
+class IPF_Admin_Model_RelationFilter extends IPF_Admin_Model_Filter
+{
+    private $relation, $selected = null;
+
+    function __construct($modelName, $relName)
+    {
+        $this->relation = IPF_ORM::getTable($modelName)->getRelation($relName);
+        $this->title = 'By '.IPF_Utils::humanTitle($relName);
+
+        $this->paramName = 'filter_'.$this->relation['local'];
+    }
+
+    function setParams($params)
+    {
+        $this->selected = \PFF\Arr::get($params, $this->paramName);
+    }
+
+    function applyToQuery($q)
+    {
+        // check ???
+        if ($this->selected)
+            $q->addWhere($this->relation['local'].' = ?', array($this->selected));
+    }
+
+    function render($extraParams)
+    {
+        $ul = Tag::ul();
+
+        // reset filter
+        $ul->append(Tag::li()
+            ->toggleClass('selected', !$this->selected)
+            ->append($this->link($extraParams, null, __('All'))));
+
+        // query related
+        $table = IPF_ORM::getTable($this->relation['class']);
+
+        $query = $table->createQuery();
+        if ($table->getOrdering()) {
+            $query->orderBy(implode(', ', $table->getOrdering()));
+        } elseif ($table->hasTemplate('IPF_ORM_Template_Orderable')) {
+            $query->orderBy($table->getTemplate('IPF_ORM_Template_Orderable')->getColumnName());
+        }
+
+        $foreign = $this->relation['foreign'];
+        foreach ($query->execute() as $val) {
+            $id = $val[$foreign];
+            $name = (string)$val;
+
+            $ul->append(Tag::li()
+                ->toggleClass('selected', $this->selected == $id)
+                ->append($this->link($extraParams, $id, $name)));
+        }
+
+        return $ul->html();
+    }
+}
+
+class IPF_Admin_Model_OwnedFilter extends IPF_Admin_Model_Filter
+{
+    private $column, $selected = null;
+
+    function __construct($modelName, $column, $title)
+    {
+        $this->column = $column;
+        $this->title = $title;
+        $this->paramName = 'filter_'.$this->column;
+    }
+
+    function setParams($params)
+    {
+        $this->selected = \PFF\Arr::get($params, $this->paramName);
+    }
+
+    function applyToQuery($q)
+    {
+        // check ???
+        if ($this->selected)
+            $q->addWhere($this->column.' = ?', array($this->selected));
+    }
+
+    function render($extraParams)
+    {
+        $ul = Tag::ul();
+
+        // reset filter
+        $ul->append(Tag::li()
+            ->toggleClass('selected', !$this->selected)
+            ->append($this->link($extraParams, null, __('All'))));
+
+        foreach (User::query()->fetchAll() as $val) {
+            $id = $val->id;
+            $name = (string)$val;
+
+            $ul->append(Tag::li()
+                ->toggleClass('selected', $this->selected == $id)
+                ->append($this->link($extraParams, $id, $name)));
+        }
+
+        return $ul->html();
+    }
+}
+
+class BooleanFilter extends IPF_Admin_Model_Filter
+{
+    private $column, $trueTitle, $falseTitle, $selected;
+
+    public function __construct($column, $title, $trueTitle='Yes', $falseTitle='No')
+    {
+        $this->column = $column;
+        $this->title = $title;
+        $this->trueTitle = $trueTitle;
+        $this->falseTitle = $falseTitle;
+
+        $this->paramName = 'filter_'.$column;
+    }
+
+    function setParams($params)
+    {
+        $this->selected = \PFF\Arr::get($params, $this->paramName);
+    }
+
+    function render($extraParams)
+    {
+        return Tag::ul(null,
+            Tag::li()
+                ->toggleClass('selected', $this->selected !== 'y' && $this->selected !== 'n')
+                ->append($this->link($extraParams, null, __('All'))),
+            Tag::li()
+                ->toggleClass('selected', $this->selected === 'y')
+                ->append($this->link($extraParams, 'y', $this->trueTitle)),
+            Tag::li()
+                ->toggleClass('selected', $this->selected === 'n')
+                ->append($this->link($extraParams, 'n', $this->falseTitle)));
+    }
+
+    function applyToQuery($q)
+    {
+        switch ($this->selected) {
+            case 'y':
+                $query->addWhere($this->column);
+                break;
+            case 'n':
+                $query->addWhere('NOT '.$this->column);
+                break;
+        }
+    }
+}
+
+class DateHierarchyListFilter extends IPF_Admin_Model_Filter
+{
+    private $model, $name;
+    private $day, $month, $year;
+
+    function __construct($title, $modelName, $fieldName)
+    {
+        $this->title = $title;
+        $this->modelName = $modelName;
+        $this->name = $fieldName;
+
+        $this->paramName = $fieldName;
+    }
+
+    function setParams($params)
+    {
+        $date = \PFF\Arr::get($params, $this->paramName, '');
+        $matches = array();
+        if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $date, $matches)) {
+            $this->year = intval($matches[1]);
+            $this->month = intval($matches[2]);
+            $this->day = intval($matches[3]);
+        }
+    }
+
+    function render($extraParams)
+    {
+        $ul = Tag::ul();
+
+        $ul->append(Tag::li()
+            ->toggleClass('selected', !$this->year)
+            ->append($this->link($extraParams, null, __('All'))));
+
+        if ($this->year)
+            $ul->append($this->renderChoice($extraParams, $this->year, 0, 0, $this->year)->addClass('selected'));
+
+        if ($this->month)
+            $ul->append($this->renderChoice($extraParams, $this->year, $this->month, 0, $this->monthName($this->month))->addClass('selected'));
+
+        if ($this->day)
+            $ul->append($this->renderChoice($extraParams, $this->year, $this->month, $this->day, $this->day)->addClass('selected'));
+
+        if ($this->day) {
+        } elseif ($this->month) {
+            $days = $this->choices('DAY(' . $this->name . ')',
+                'YEAR(' . $this->name . ')', $this->year,
+                'MONTH(' . $this->name . ')', $this->month);
+            foreach ($days as $day) {
+                $ul->append($this->renderChoice($extraParams, $this->year, $this->month, $day, $day));
+            }
+        } elseif ($this->year) {
+            $months = $this->choices('MONTH(' . $this->name . ')',
+                'YEAR(' . $this->name . ')', $this->year);
+            foreach ($months as $month) {
+                $ul->append($this->renderChoice($extraParams, $this->year, $month, 0, $this->monthName($month)));
+            }
+        } else {
+            $years = $this->choices('YEAR(' . $this->name . ')');
+            foreach ($years as $year) {
+                $ul->append($this->renderChoice($extraParams, $year, 0, 0, $year));
+            }
+        }
+
+        return $ul->html();
+    }
+
+    function applyToQuery($q)
+    {
+        if ($this->day)
+            $q->addWhere('DAY(' . $this->name . ') = ?', array($this->day));
+        if ($this->month)
+            $q->addWhere('MONTH(' . $this->name . ') = ?', array($this->month));
+        if ($this->year)
+            $q->addWhere('YEAR(' . $this->name . ') = ?', array($this->year));
+    }
+
+    private function choices(/* $what, [$expr, $value, ...]*/)
+    {
+        $args = func_get_args();
+        $what = array_shift($args);
+
+        $q = IPF_ORM_Query::create()
+            ->from($this->modelName)
+            ->select($what . ' AS value')
+            ->groupBy('1')
+            ->orderBy('1');
+
+        while ($args) {
+            $expr = array_shift($args);
+            $value = array_shift($args);
+
+            $q->addWhere($expr . ' = ?', array($value));
+        }
+        return \PFF\Arr::create($q->fetchArray())->pluck('value')->arr();
+    }
+
+    private function renderChoice($extraParams, $year, $month, $day, $label)
+    {
+        return Tag::li(null,
+            $this->link($extraParams, sprintf('%04d-%02d-%02d', $year, $month, $day), $label));
+    }
+
+    protected function monthName($month)
+    {
+        return date('F', mktime(0, 0, 0, $month, 1));
+    }
+}
+
diff --git a/src/adminmodelinline.php b/src/adminmodelinline.php
new file mode 100644 (file)
index 0000000..bb5e4dd
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+
+abstract class IPF_Admin_ModelInline
+{
+    public $parentModel = null;
+    public $orderby = 'id';
+
+    function __construct($parentModel)
+    {
+        $this->parentModel = $parentModel;
+    }
+
+    abstract function getModelName();
+
+    public function getApplication()
+    {
+        foreach (IPF_Project::getInstance()->appList() as $app)
+            foreach (IPF_Legacy_ORM_App::appModelList($app) as $model)
+                if ($model == $this->getModelName())
+                    return $app;
+        return null;
+    }
+
+    function getAddNum()
+    {
+        return 4;
+    }
+
+    function getLegend()
+    {
+        return $this->getModelName();
+    }
+
+    function getFkName()
+    {
+        foreach($this->getTable()->getRelations() as $rel) {
+            if ($rel->getClass()==get_class($this->parentModel))
+                return $rel->getAlias();
+        }
+        throw new IPF_Exception(__('Cannot get fkName for '.$this->getModelName()));
+    }
+
+    function getFkLocal()
+    {
+        foreach($this->getTable()->getRelations() as $rel) {
+            if ($rel->getClass() == get_class($this->parentModel))
+                return $rel->getLocal();
+        }
+        throw new IPF_Exception(__('Cannot get fkLocal for '.$this->getModelName()));
+    }
+
+    function getObjects()
+    {
+        if (!$this->parentModel->exists())
+            return array();
+
+        $query = IPF_ORM_Query::create()
+            ->from($this->getModelName())
+            ->where($this->getFkLocal().'='.$this->parentModel->id);
+
+        $o = $this->_orderableColumn();
+        if ($o)
+            $query->orderby($o);
+        else
+            $query->orderby($this->orderby);
+
+        return $query->execute();
+    }
+
+    public function _orderable()
+    {
+        return $this->_orderableColumn() !== null;
+    }
+
+    public function _orderableColumn()
+    {
+        if ($this->getTable()->hasTemplate('IPF_ORM_Template_Orderable'))
+            return $this->getTable()->getTemplate('IPF_ORM_Template_Orderable')->getColumnName();
+        else
+            return null;
+    }
+
+    private function getTable()
+    {
+        return IPF_ORM::getTable($this->getModelName());
+    }
+
+    public function createField()
+    {
+        $exclude = array(
+            $this->getFkName(),
+            $this->getFkLocal(),
+        );
+        $o = $this->_orderableColumn();
+        if ($o)
+            $exclude[] = $o;
+
+        $fields = array();
+        foreach (IPF_Form_Model::suggestFields($this->getTable(), null, $exclude, null) as $field) {
+            list($n, $f) = $field;
+            $fields[$n] = $f;
+        }
+
+        return new IPF_Form_Field_Set(array(
+            'fields' => $fields,
+            'addCount' => $this->getAddNum(),
+        ));
+    }
+}
+
diff --git a/src/app.php b/src/app.php
new file mode 100644 (file)
index 0000000..8db81a0
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+class IPF_Legacy_ORM_App extends IPF_Application
+{
+    private static $appModels = array();
+
+    public static function appModelList($app)
+    {
+        if (!array_key_exists($app->getName(), self::$appModels)) {
+            $models = array();
+            try {
+                $it = new DirectoryIterator($app->getPath().DIRECTORY_SEPARATOR.'models');
+                foreach ($it as $file) {
+                    $e = explode('.', $file->getFileName(), 2);
+                    if (count($e) == 2 && $e[1] === 'php') {
+                        $models[] = $e[0];
+                    }
+                }
+            } catch (RuntimeException $e) {
+                // nothing to do
+            }
+
+            self::$appModels[$app->getName()] = $models;
+        }
+        return self::$appModels[$app->getName()];
+    }
+
+    public function commands()
+    {
+        return array(
+            new IPF_Legacy_ORM_Command_BuildModels,
+            new IPF_Legacy_ORM_Command_Sql,
+            new IPF_Legacy_ORM_Command_SyncDB,
+            new IPF_Legacy_ORM_Command_Fixtures,
+        );
+    }
+}
+
diff --git a/src/commands/buildmodels.php b/src/commands/buildmodels.php
new file mode 100644 (file)
index 0000000..9dca874
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+class IPF_Legacy_ORM_Command_BuildModels
+{
+    public $command = 'buildmodels';
+    public $description = 'Build all model classes';
+
+    public function run($args=null)
+    {
+        print "Build all model classes\n\n";
+
+        $project = IPF_Project::getInstance();
+
+        $paths = array();
+        foreach ($project->appList() as $app)
+            $paths[] = $app->getPath();
+
+        IPF_ORM::generateModelsFromYaml($paths, array());
+    }
+}
+
diff --git a/src/commands/fixtures.php b/src/commands/fixtures.php
new file mode 100644 (file)
index 0000000..3c6e279
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+class IPF_Legacy_ORM_Command_Fixtures
+{
+    public $command = 'fixtures';
+    public $description = 'Load fixtures into database';
+
+    public function run($args=null)
+    {
+        print "Load project fixtures to database\n";
+
+        $project = IPF_Project::getInstance();
+
+        $paths = array(IPF::get('project_path'));
+        foreach ($project->appList() as $app)
+            $paths[] = $app->path;
+
+        $fixtures = array();
+        foreach ($paths as $path) {
+            $path .= DIRECTORY_SEPARATOR.'fixtures.php';
+            if (is_file($path))
+                $fixtures = array_merge($fixtures, require $path);
+        }
+
+        if (!count($fixtures)) {
+            echo "No fixtures found\n";
+            return;
+        }
+
+        foreach ($fixtures as $fixture) {
+            $modelClass = $fixture['model'];
+            $key = $fixture['key'];
+            if (!is_array($key))
+                $key = array($key);
+            $records = $fixture['records'];
+            echo "Loading $modelClass ";
+            $table = IPF_ORM::getTable($modelClass);
+            $table->getConnection()->beginTransaction();
+
+            $query = $table
+                ->createQuery()
+                ->limit(1);
+            foreach ($key as $k)
+                $query->addWhere($k . ' = ?');
+
+            foreach ($records as $record) {
+                $params = array();
+                foreach ($key as $k)
+                    $params[] = $record[$k];
+
+                $model = $query->execute($params);
+                if ($model)
+                    $model = $model[0];
+                else
+                    $model = new $modelClass;
+
+                foreach ($record as $k => $v)
+                    $model->$k = $v;
+
+                $model->save();
+                echo '.';
+            }
+            $table->getConnection()->commit();
+            echo "\n";
+        }
+    }
+}
+
diff --git a/src/commands/sql.php b/src/commands/sql.php
new file mode 100644 (file)
index 0000000..31f55c9
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+class IPF_Legacy_ORM_Command_Sql
+{
+    public $command = 'sql';
+    public $description = 'Show all SQL DDL from model classes';
+
+    public function run($args=null)
+    {
+        print "Show all SQL DDL from model classes\n";
+
+        foreach (IPF_Project::getInstance()->appList() as $app)
+            print IPF_ORM::generateSqlFromModels($app)."\n";
+    }
+}
+
diff --git a/src/commands/syncdb.php b/src/commands/syncdb.php
new file mode 100644 (file)
index 0000000..02b550f
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+class IPF_Legacy_ORM_Command_SyncDB
+{
+    public $command = 'syncdb';
+    public $description = 'Create tables from model classes';
+
+    public function run($args=null)
+    {
+        print "Create tables from model classes\n";
+
+        $project = IPF_Project::getInstance();
+
+        $models = array();
+        foreach ($project->appList() as $app)
+            $models = array_merge($models, IPF_Legacy_ORM_App::appModelList($app));
+
+        IPF_ORM::createTablesFromModels($models);
+    }
+}
+
diff --git a/src/modelform.php b/src/modelform.php
new file mode 100644 (file)
index 0000000..fe9cb93
--- /dev/null
@@ -0,0 +1,223 @@
+<?php
+
+class IPF_Form_Model extends IPF_Form
+{
+    public $model = null;
+    public $user_fields = null;
+
+    function initFields($extra=array())
+    {
+        if (isset($extra['model']))
+            $this->model = $extra['model'];
+        else
+            throw new IPF_Exception_Form(__('Unknown model for form'));
+
+        if (isset($extra['user_fields']))
+            $this->user_fields = $extra['user_fields'];
+
+        $exclude = \PFF\Arr::get($extra, 'exclude', array());
+        $fields = self::suggestFields($this->model->getTable(), $this->fields(), $exclude, $this);
+        foreach ($fields as $field) {
+            list($n, $f) = $field;
+            $this->fields[$n] = $f;
+        }
+    }
+
+    public static function suggestFields($table, $fields=null, $exclude=array(), $form=null)
+    {
+        $result = array();
+
+        $db_columns = $table->getColumns();
+        $db_relations = $table->getRelations();
+
+        if ($fields === null) {
+            foreach ($db_columns as $name => $col) {
+                if (array_search($name, $exclude) !== false)
+                    continue;
+                if (isset($col['exclude']) && $col['exclude'])
+                    continue;
+
+                if ($form && method_exists($form, 'add__'.$name.'__field')) {
+                    $f = call_user_func(array($form, 'add__'.$name.'__field'));
+                    if ($f)
+                        $field = array($name, $f);
+                } else {
+                    $field = self::createDBField($name, $table, $col);
+                }
+
+                if ($field)
+                    $result[] = $field;
+            }
+
+            foreach ($db_relations as $name => $relation) {
+                if (array_search($name, $exclude) !== false)
+                    continue;
+                if (isset($relation['exclude']) && $relation['exclude'])
+                    continue;
+
+                if ($form && method_exists($form, 'add__'.$name.'__field')) {
+                    $f = call_user_func(array($form, 'add__'.$name.'__field'));
+                    if ($f)
+                        $field = array($name, $f);
+                } else {
+                    $field = self::createDBRelation($name, $relation, $db_columns);
+                }
+
+                if ($field)
+                    $result[] = $field;
+            }
+        } else {
+            foreach ($fields as $uname) {
+                $field = null;
+                if ($form && method_exists($form, 'add__'.$uname.'__field')) {
+                    $f = call_user_func(array($form, 'add__'.$uname.'__field'));
+                    if ($f)
+                        $field = array($uname, $f);
+                } elseif (array_key_exists($uname, $db_columns)) {
+                    $field = self::createDBField($uname, $table, $db_columns[$uname]);
+                } elseif (array_key_exists($uname, $db_relations)) {
+                    $field = self::createDBRelation($uname, $db_relations[$uname], $db_columns);
+                } else {
+                    throw new IPF_Exception_Form(sprintf(__("Model '%s' has no column '%s'."), $table->getComponentName(), $uname));
+                }
+                if ($field)
+                    $result[] = $field;
+            }
+        }
+        return $result;
+    }
+
+    public static function createDBField($name, $table, $col)
+    {
+        if ($name == $table->getIdentifier())
+            return null;
+
+        $required = isset($col['notblank']) && $col['notblank'];
+        $label = isset($col['verbose']) ? $col['verbose'] : IPF_Utils::humanTitle($name);
+
+        $params = array(
+            'required'  => $required,
+            'label'     => $label,
+            'help_text' => '',
+        );
+
+        switch ($col['type']) {
+            case 'string':
+                if (isset($col['length']))
+                    $params['max_length'] = (int)($col['length']);
+
+                if (isset($col['uploadTo']))
+                    $params['uploadTo'] = $col['uploadTo'];
+
+                if     (isset($col['email']) && $col['email'])  { $form_field = new IPF_Form_Field_Email($params); }
+                elseif (isset($col['file'])  && $col['file'])   { $form_field = new IPF_Form_Field_File($params); }
+                elseif (isset($col['image']) && $col['image'])  { $form_field = new IPF_Form_Field_Image($params); }
+                elseif (isset($col['html'])  && $col['html'])   { $form_field = new IPF_Form_Field_Html($params); }
+                else                                            { $form_field = new IPF_Form_Field_Varchar($params); }
+
+                break;
+            case 'boolean':
+                $form_field = new IPF_Form_Field_Boolean($params);
+                break;
+            case 'integer':
+                $params['widget_attrs'] = array('style' => 'width:140px;');
+                $form_field = new IPF_Form_Field_Integer($params);
+                break;
+            case 'double':
+            case 'decimal':
+                $params['widget_attrs'] = array('style' => 'width:140px;');
+                $form_field = new IPF_Form_Field_Float($params);
+                break;
+            case 'date':
+                $format = IPF::get('date_format');
+                if ($format)
+                    $params['widget_attrs'] = array('format' => $format);
+                $form_field = new IPF_Form_Field_Date($params);
+                break;
+            case 'datetime':
+            case 'timestamp':
+                $format = IPF::get('datetime_format');
+                if ($format)
+                    $params['widget_attrs'] = array('format' => $format);
+                $form_field = new IPF_Form_Field_Datetime($params);
+                break;
+            default:
+                throw new IPF_Exception_Form(__('Unsupported column type \''.$col['type'].'\'.'));
+        }
+
+        if ($form_field !== null)
+            return array($name, $form_field);
+        else
+            return null;
+    }
+
+    public static function createDBRelation($name, $relation, $db_columns)
+    {
+        $rt = $relation->getType();
+        if ($rt !== IPF_ORM_Relation::ONE_AGGREGATE && $rt !== IPF_ORM_Relation::MANY_AGGREGATE)
+            return null;
+
+        $lfn = $relation->getLocalFieldName();
+        if (isset($db_columns[$lfn]))
+            $col = $db_columns[$lfn];
+        else
+            $col = array();
+
+        $table = IPF_ORM::getTable($relation->getClass());
+
+        $params = array(
+            'required'  => isset($col['notblank']),
+            'label'     => isset($col['verbose']) ? $col['verbose'] : IPF_Utils::humanTitle($name),
+            'help_text' => '',
+            'table'     => $table,
+            'model'     => $relation->getClass(),
+        );
+
+        if ($rt === IPF_ORM_Relation::ONE_AGGREGATE) {
+            $choices = array('--------' => '');
+            $pk = $table->getIdentifier();
+            foreach ($table->findAll() as $o) {
+                $choices[$o->__toString()] = $o->$pk;
+            }
+
+            $params['choices'] = $choices;
+            return array($name.'_id', new IPF_Form_Field_Choice($params));
+        } elseif ($rt === IPF_ORM_Relation::MANY_AGGREGATE) {
+            $choices = array();
+            $pk = $table->getIdentifier();
+            foreach ($table->findAll() as $o) {
+                $choices[$o->__toString()] = $o->$pk;
+            }
+
+            $params['choices'] = $choices;
+            return array($name, new IPF_Form_Field_MultipleChoice($params));
+        } else {
+            return null;
+        }
+    }
+
+    function fields()
+    {
+        return $this->user_fields;
+    }
+
+    function save($commit=true)
+    {
+        if (!$this->isValid())
+            throw new IPF_Exception_Form(__('Cannot save the model from an invalid form.'));
+
+        $this->model->SetFromFormData($this->cleaned_data);
+        $this->model->save();
+        $rels = $this->model->getTable()->getRelations();
+        foreach ($rels as $rname => $rel) {
+            if (isset($this->cleaned_data[$rname])) {
+                $this->model->unlink($rel->getAlias());
+                if (is_array($this->cleaned_data[$rname])) {
+                    $this->model->link($rel->getAlias(),$this->cleaned_data[$rname]);
+                }
+            }
+        }
+        return $this->model;
+    }
+}
+
diff --git a/src/orm.php b/src/orm.php
new file mode 100644 (file)
index 0000000..f5fb916
--- /dev/null
@@ -0,0 +1,255 @@
+<?php
+
+final class IPF_ORM
+{
+    const ERR                       = -1;
+    const ERR_SYNTAX                = -2;
+    const ERR_CONSTRAINT            = -3;
+    const ERR_NOT_FOUND             = -4;
+    const ERR_ALREADY_EXISTS        = -5;
+    const ERR_UNSUPPORTED           = -6;
+    const ERR_MISMATCH              = -7;
+    const ERR_INVALID               = -8;
+    const ERR_NOT_CAPABLE           = -9;
+    const ERR_TRUNCATED             = -10;
+    const ERR_INVALID_NUMBER        = -11;
+    const ERR_INVALID_DATE          = -12;
+    const ERR_DIVZERO               = -13;
+    const ERR_NODBSELECTED          = -14;
+    const ERR_CANNOT_CREATE         = -15;
+    const ERR_CANNOT_DELETE         = -16;
+    const ERR_CANNOT_DROP           = -17;
+    const ERR_NOSUCHTABLE           = -18;
+    const ERR_NOSUCHFIELD           = -19;
+    const ERR_NEED_MORE_DATA        = -20;
+    const ERR_NOT_LOCKED            = -21;
+    const ERR_VALUE_COUNT_ON_ROW    = -22;
+    const ERR_INVALID_DSN           = -23;
+    const ERR_CONNECT_FAILED        = -24;
+    const ERR_EXTENSION_NOT_FOUND   = -25;
+    const ERR_NOSUCHDB              = -26;
+    const ERR_ACCESS_VIOLATION      = -27;
+    const ERR_CANNOT_REPLACE        = -28;
+    const ERR_CONSTRAINT_NOT_NULL   = -29;
+    const ERR_DEADLOCK              = -30;
+    const ERR_CANNOT_ALTER          = -31;
+    const ERR_MANAGER               = -32;
+    const ERR_MANAGER_PARSE         = -33;
+    const ERR_LOADMODULE            = -34;
+    const ERR_INSUFFICIENT_DATA     = -35;
+    const ERR_CLASS_NAME            = -36;
+
+    const CASE_LOWER = 2;
+    const CASE_NATURAL = 0;
+    const CASE_UPPER = 1;
+    const CURSOR_FWDONLY = 0;
+    const CURSOR_SCROLL = 1;
+    const ERRMODE_EXCEPTION = 2;
+    const ERRMODE_SILENT = 0;
+    const ERRMODE_WARNING = 1;
+    const FETCH_ASSOC = 2;
+    const FETCH_BOTH = 4;
+    const FETCH_BOUND = 6;
+    const FETCH_CLASS = 8;
+    const FETCH_CLASSTYPE = 262144;
+    const FETCH_COLUMN = 7;
+    const FETCH_FUNC = 10;
+    const FETCH_GROUP = 65536;
+    const FETCH_INTO = 9;
+    const FETCH_LAZY = 1;
+    const FETCH_NAMED = 11;
+    const FETCH_NUM = 3;
+    const FETCH_OBJ = 5;
+    const FETCH_ORI_ABS = 4;
+    const FETCH_ORI_FIRST = 2;
+    const FETCH_ORI_LAST = 3;
+    const FETCH_ORI_NEXT = 0;
+    const FETCH_ORI_PRIOR = 1;
+    const FETCH_ORI_REL = 5;
+    const FETCH_SERIALIZE = 524288;
+    const FETCH_UNIQUE = 196608;
+    const NULL_EMPTY_STRING = 1;
+    const NULL_NATURAL = 0;
+    const NULL_TO_STRING         = NULL;
+    const PARAM_BOOL = 5;
+    const PARAM_INPUT_OUTPUT = -2147483648;
+    const PARAM_INT = 1;
+    const PARAM_LOB = 3;
+    const PARAM_NULL = 0;
+    const PARAM_STMT = 4;
+    const PARAM_STR = 2;
+
+    const ATTR_AUTOCOMMIT           = 0;
+    const ATTR_PREFETCH             = 1;
+    const ATTR_TIMEOUT              = 2;
+    const ATTR_ERRMODE              = 3;
+    const ATTR_SERVER_VERSION       = 4;
+    const ATTR_CLIENT_VERSION       = 5;
+    const ATTR_SERVER_INFO          = 6;
+    const ATTR_CONNECTION_STATUS    = 7;
+    const ATTR_CASE                 = 8;
+    const ATTR_CURSOR_NAME          = 9;
+    const ATTR_CURSOR               = 10;
+    const ATTR_ORACLE_NULLS         = 11;
+    const ATTR_PERSISTENT           = 12;
+    const ATTR_STATEMENT_CLASS      = 13;
+    const ATTR_FETCH_TABLE_NAMES    = 14;
+    const ATTR_FETCH_CATALOG_NAMES  = 15;
+    const ATTR_STRINGIFY_FETCHES    = 17;
+    const ATTR_MAX_COLUMN_LEN       = 18;
+
+    const ATTR_FIELD_CASE           = 102;
+    const ATTR_CMPNAME_FORMAT       = 118;
+    const ATTR_DBNAME_FORMAT        = 117;
+    const ATTR_TBLCLASS_FORMAT      = 119;
+    const ATTR_EXPORT               = 140;
+    const ATTR_DECIMAL_PLACES       = 141;
+
+    const ATTR_PORTABILITY          = 106;
+    const ATTR_COLL_KEY             = 108;
+    const ATTR_DEFAULT_TABLE_TYPE   = 112;
+    const ATTR_DEF_TEXT_LENGTH      = 113;
+    const ATTR_DEF_VARCHAR_LENGTH   = 114;
+    const ATTR_DEF_TABLESPACE       = 115;
+    const ATTR_EMULATE_DATABASE     = 116;
+    const ATTR_USE_NATIVE_ENUM      = 117;
+
+    //const ATTR_FETCHMODE                = 118;
+    const ATTR_NAME_PREFIX              = 121;
+    const ATTR_CREATE_TABLES            = 122;
+    const ATTR_COLL_LIMIT               = 123;
+
+    const ATTR_CACHE                    = 150;
+    const ATTR_RESULT_CACHE             = 150;
+    const ATTR_CACHE_LIFESPAN           = 151;
+    const ATTR_RESULT_CACHE_LIFESPAN    = 151;
+    const ATTR_LOAD_REFERENCES          = 153;
+    const ATTR_DEFAULT_PARAM_NAMESPACE  = 156;
+    const ATTR_QUERY_CACHE              = 157;
+    const ATTR_QUERY_CACHE_LIFESPAN     = 158;
+    const ATTR_MODEL_LOADING            = 161;
+    const ATTR_RECURSIVE_MERGE_FIXTURES = 162;
+    const ATTR_SINGULARIZE_IMPORT       = 163;
+
+    const FETCH_IMMEDIATE       = 0;
+    const FETCH_BATCH           = 1;
+    const FETCH_OFFSET          = 3;
+    const FETCH_LAZY_OFFSET     = 4;
+    const FETCH_VHOLDER         = 1;
+    const FETCH_RECORD          = 2;
+    const FETCH_ARRAY           = 3;
+
+    const PORTABILITY_NONE          = 0;
+    const PORTABILITY_FIX_CASE      = 1;
+    const PORTABILITY_RTRIM         = 2;
+    const PORTABILITY_DELETE_COUNT  = 4;
+    const PORTABILITY_EMPTY_TO_NULL = 8;
+    const PORTABILITY_FIX_ASSOC_FIELD_NAMES = 16;
+    const PORTABILITY_EXPR          = 32;
+    const PORTABILITY_ALL           = 63;
+
+    const LOCK_OPTIMISTIC       = 0;
+    const LOCK_PESSIMISTIC      = 1;
+
+    const EXPORT_NONE               = 0;
+    const EXPORT_TABLES             = 1;
+    const EXPORT_CONSTRAINTS        = 2;
+    const EXPORT_ALL                = 7;
+
+    const HYDRATE_RECORD            = 2;
+    const HYDRATE_ARRAY             = 3;
+
+    const HYDRATE_NONE              = 4;
+    const IDENTIFIER_AUTOINC        = 1;
+    const IDENTIFIER_NATURAL        = 3;
+    const IDENTIFIER_COMPOSITE      = 4;
+    const MODEL_LOADING_AGGRESSIVE   = 1;
+    const MODEL_LOADING_CONSERVATIVE= 2;
+    
+    const BASE_CLASSES_DIRECTORY    = '_generated';
+
+    private function __construct() {}
+
+    public static function getTable($componentName)
+    {
+        return IPF_ORM_Manager::connection()->getTable($componentName);
+    }
+
+    public static function generateModelsFromYaml($directory, $extraAllwedReferences=array())
+    {
+        $files = array();
+        foreach ((array)$directory as $dir)
+            $files[] = $dir . '/models.yml';
+
+        // load schema
+        $import = new IPF_ORM_Import_Schema;
+        $definitions = $import->importSchema($files, $extraAllwedReferences);
+
+        // build
+        $models = array();
+        foreach ($definitions as $name => $definition) {
+            print "    $name\n";
+            $targetPath = substr($definition['input_file'], 0, -4);
+
+            $builder = new IPF_ORM_Import_Builder;
+            $builder->buildRecord($definition, $targetPath);
+
+            $models[] = $name;
+        }
+
+        return $models;
+    }
+
+    public static function createTablesFromModels($models)
+    {
+        IPF_ORM_Manager::connection()->export->exportClasses($models);
+    }
+
+    public static function generateSqlFromModels($app)
+    {
+        $sql = IPF_ORM_Manager::connection()->export->exportSortedClassesSql(IPF_Legacy_ORM_App::appModelList($app), false);
+
+        $build = '';
+        foreach ($sql as $query) {
+            $build .= $query.";\n\n";
+        }
+        return $build;
+    }
+
+    public static function dump($var, $output = true, $indent = "")
+    {
+        $ret = array();
+        switch (gettype($var)) {
+            case 'array':
+                $ret[] = 'Array(';
+                $indent .= "    ";
+                foreach ($var as $k => $v) {
+
+                    $ret[] = $indent . $k . ' : ' . self::dump($v, false, $indent);
+                }
+                $indent = substr($indent,0, -4);
+                $ret[] = $indent . ")";
+                break;
+            case 'object':
+                $ret[] = 'Object(' . get_class($var) . ')';
+                break;
+            default:
+                $ret[] = var_export($var, true);
+        }
+
+        if ($output) {
+            print implode("\n", $ret);
+        }
+
+        return implode("\n", $ret);
+    }
+
+    public static function GetObjectOr404($object, $id)
+    {
+        $obj = IPF_ORM::getTable($object)->findOneById($id);
+        if ($obj)
+            return $obj;
+        throw new IPF_HTTP_Error404();
+    }
+}
+
diff --git a/src/orm/access.php b/src/orm/access.php
new file mode 100644 (file)
index 0000000..d0a87d2
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+abstract class IPF_ORM_Access implements ArrayAccess
+{
+    public function setArray(array $array)
+    {
+        foreach ($array as $k => $v) {
+            $this->set($k, $v);
+        }
+
+        return $this;
+    }
+
+    public function __set($name, $value)
+    {
+        $this->set($name, $value);
+    }
+
+    public function __get($name)
+    {
+        return $this->get($name);
+    }
+
+    public function __isset($name)
+    {
+        return $this->contains($name);
+    }
+
+    public function __unset($name)
+    {
+        return $this->remove($name);
+    }
+
+    public function offsetExists($offset)
+    {
+        return $this->contains($offset);
+    }
+
+    public function offsetGet($offset)
+    {
+        return $this->get($offset);
+    }
+
+    public function offsetSet($offset, $value)
+    {
+        if ( ! isset($offset)) {
+            $this->add($value);
+        } else {
+            $this->set($offset, $value);
+        }
+    }
+
+    public function offsetUnset($offset)
+    {
+        return $this->remove($offset);
+    }
+
+    public function remove($offset)
+    {
+        throw new IPF_ORM_Exception('Remove is not supported for ' . get_class($this));
+    }
+
+    public function get($offset)
+    {
+        throw new IPF_ORM_Exception('Get is not supported for ' . get_class($this));
+    }
+
+    public function set($offset, $value)
+    {
+        throw new IPF_ORM_Exception('Set is not supported for ' . get_class($this));
+    }
+
+    public function contains($offset)
+    {
+        throw new IPF_ORM_Exception('Contains is not supported for ' . get_class($this));
+    }
+
+    public function add($value)
+    {
+        throw new IPF_ORM_Exception('Add is not supported for ' . get_class($this));
+    }
+}
diff --git a/src/orm/adapter.php b/src/orm/adapter.php
new file mode 100644 (file)
index 0000000..363cade
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+
+class IPF_ORM_Adapter
+{
+    const ATTR_AUTOCOMMIT = 0;
+    const ATTR_CASE = 8;
+    const ATTR_CLIENT_VERSION = 5;
+    const ATTR_CONNECTION_STATUS = 7;
+    const ATTR_CURSOR = 10;
+    const ATTR_CURSOR_NAME = 9;
+    const ATTR_ERRMODE = 3;
+    const ATTR_FETCH_CATALOG_NAMES = 15;
+    const ATTR_FETCH_TABLE_NAMES = 14;
+    const ATTR_MAX_COLUMN_LEN = 18;
+    const ATTR_ORACLE_NULLS = 11;
+    const ATTR_PERSISTENT = 12;
+    const ATTR_PREFETCH = 1;
+    const ATTR_SERVER_INFO = 6;
+    const ATTR_SERVER_VERSION = 4;
+    const ATTR_STATEMENT_CLASS = 13;
+    const ATTR_STRINGIFY_FETCHES = 17;
+    const ATTR_TIMEOUT = 2;
+    const CASE_LOWER = 2;
+    const CASE_NATURAL = 0;
+    const CASE_UPPER = 1;
+    const CURSOR_FWDONLY = 0;
+    const CURSOR_SCROLL = 1;
+    const ERR_ALREADY_EXISTS = NULL;
+    const ERR_CANT_MAP = NULL;
+    const ERR_CONSTRAINT = NULL;
+    const ERR_DISCONNECTED = NULL;
+    const ERR_MISMATCH = NULL;
+    const ERR_NO_PERM = NULL;
+    const ERR_NONE = '00000';
+    const ERR_NOT_FOUND = NULL;
+    const ERR_NOT_IMPLEMENTED = NULL;
+    const ERR_SYNTAX = NULL;
+    const ERR_TRUNCATED = NULL;
+    const ERRMODE_EXCEPTION = 2;
+    const ERRMODE_SILENT = 0;
+    const ERRMODE_WARNING = 1;
+    const FETCH_ASSOC = 2;
+    const FETCH_BOTH = 4;
+    const FETCH_BOUND = 6;
+    const FETCH_CLASS = 8;
+    const FETCH_CLASSTYPE = 262144;
+    const FETCH_COLUMN = 7;
+    const FETCH_FUNC = 10;
+    const FETCH_GROUP = 65536;
+    const FETCH_INTO = 9;
+    const FETCH_LAZY = 1;
+    const FETCH_NAMED = 11;
+    const FETCH_NUM = 3;
+    const FETCH_OBJ = 5;
+    const FETCH_ORI_ABS = 4;
+    const FETCH_ORI_FIRST = 2;
+    const FETCH_ORI_LAST = 3;
+    const FETCH_ORI_NEXT = 0;
+    const FETCH_ORI_PRIOR = 1;
+    const FETCH_ORI_REL = 5;
+    const FETCH_SERIALIZE = 524288;
+    const FETCH_UNIQUE = 196608;
+    const NULL_EMPTY_STRING = 1;
+    const NULL_NATURAL = 0;
+    const NULL_TO_STRING = NULL;
+    const PARAM_BOOL = 5;
+    const PARAM_INPUT_OUTPUT = -2147483648;
+    const PARAM_INT = 1;
+    const PARAM_LOB = 3;
+    const PARAM_NULL = 0;
+    const PARAM_STMT = 4;
+    const PARAM_STR = 2;
+}
diff --git a/src/orm/cache/interface.php b/src/orm/cache/interface.php
new file mode 100644 (file)
index 0000000..f88a509
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+interface IPF_ORM_Cache_Interface 
+{
+    public function fetch($id, $testCacheValidity = true);
+    public function contains($id);
+    public function save($id, $data, $lifeTime = false);
+    public function delete($id);
+}
diff --git a/src/orm/collection.php b/src/orm/collection.php
new file mode 100644 (file)
index 0000000..9bf5e52
--- /dev/null
@@ -0,0 +1,556 @@
+<?php
+
+class IPF_ORM_Collection extends IPF_ORM_Access implements Countable, IteratorAggregate, Serializable
+{
+    protected $data = array();
+    protected $_table;
+    protected $_snapshot = array();
+    protected $reference;
+    protected $referenceField;
+    protected $relation;
+    protected $keyColumn;
+
+    public function __construct($table, $keyColumn = null)
+    {
+        if ( ! ($table instanceof IPF_ORM_Table)) {
+            $table = IPF_ORM::getTable($table);
+        }
+
+        $this->_table = $table;
+
+        if ($keyColumn === null) {
+            $keyColumn = $table->getBoundQueryPart('indexBy');
+        }
+
+        if ($keyColumn === null) {
+               $keyColumn = $table->getAttribute(IPF_ORM::ATTR_COLL_KEY);
+        }
+
+        if ($keyColumn !== null) {
+            $this->keyColumn = $keyColumn;
+        }
+    }
+
+    public function getTable()
+    {
+        return $this->_table;
+    }
+
+    public function setData(array $data) 
+    {
+        $this->data = $data;
+    }
+
+    public function serialize()
+    {
+        $vars = get_object_vars($this);
+
+        unset($vars['reference']);
+        unset($vars['reference_field']);
+        unset($vars['relation']);
+        unset($vars['expandable']);
+        unset($vars['expanded']);
+        unset($vars['generator']);
+
+        $vars['_table'] = $vars['_table']->getComponentName();
+
+        return serialize($vars);
+    }
+
+    public function unserialize($serialized)
+    {
+        $connection = IPF_ORM_Manager::connection();
+
+        $array = unserialize($serialized);
+
+        foreach ($array as $name => $values) {
+            $this->$name = $values;
+        }
+
+        $this->_table = $connection->getTable($this->_table);
+
+        $keyColumn = isset($array['keyColumn']) ? $array['keyColumn'] : null;
+        if ($keyColumn === null) {
+            $keyColumn = $this->_table->getBoundQueryPart('indexBy');
+        }
+
+        if ($keyColumn !== null) {
+            $this->keyColumn = $keyColumn;
+        }
+    }
+
+    public function setKeyColumn($column)
+    {
+        $this->keyColumn = $column;
+        
+        return $this;
+    }
+
+    public function getKeyColumn()
+    {
+        return $this->keyColumn;
+    }
+
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    public function getFirst()
+    {
+        return reset($this->data);
+    }
+
+    public function getLast()
+    {
+        return end($this->data);
+    }
+
+    public function end()
+    {
+        return end($this->data);
+    }
+
+    public function key()
+    {
+        return key($this->data);
+    }
+
+    public function setReference(IPF_ORM_Record $record, IPF_ORM_Relation $relation)
+    {
+        $this->reference = $record;
+        $this->relation  = $relation;
+
+        if ($relation instanceof IPF_ORM_Relation_ForeignKey || 
+            $relation instanceof IPF_ORM_Relation_LocalKey) {
+
+            $this->referenceField = $relation->getForeignFieldName();
+
+            $value = $record->get($relation->getLocalFieldName());
+
+            foreach ($this->data as $record) {
+                if ($value !== null) {
+                    $record->set($this->referenceField, $value, false);
+                } else {
+                    $record->set($this->referenceField, $this->reference, false);
+                }
+            }
+        } elseif ($relation instanceof IPF_ORM_Relation_Association) {
+
+        }
+    }
+
+    public function getReference()
+    {
+        return $this->reference;
+    }
+
+    public function remove($key)
+    {
+        $removed = $this->data[$key];
+
+        unset($this->data[$key]);
+        return $removed;
+    }
+
+    public function contains($key)
+    {
+        return isset($this->data[$key]);
+    }
+
+    public function search(IPF_ORM_Record $record)
+    {
+        return array_search($record, $this->data, true);
+    }
+
+    public function get($key)
+    {
+        if ( ! isset($this->data[$key])) {
+            $record = $this->_table->create();
+
+            if (isset($this->referenceField)) {
+                $value = $this->reference->get($this->relation->getLocalFieldName());
+
+                if ($value !== null) {
+                    $record->set($this->referenceField, $value, false);
+                } else {
+                    $record->set($this->referenceField, $this->reference, false);
+                }
+            }
+            if ($key === null) {
+                $this->data[] = $record;
+            } else {
+                $this->data[$key] = $record;
+            }
+
+            if (isset($this->keyColumn)) {
+                $record->set($this->keyColumn, $key);
+            }
+
+            return $record;
+        }
+
+        return $this->data[$key];
+    }
+
+    public function getPrimaryKeys()
+    {
+        $list = array();
+        $name = $this->_table->getIdentifier();
+
+        foreach ($this->data as $record) {
+            if (is_array($record) && isset($record[$name])) {
+                $list[] = $record[$name];
+            } else {
+                $list[] = $record->getIncremented();
+            }
+        }
+        return $list;
+    }
+
+    public function getKeys()
+    {
+        return array_keys($this->data);
+    }
+
+    public function count()
+    {
+        return count($this->data);
+    }
+
+    public function set($key, $record)
+    {
+        if (isset($this->referenceField)) {
+            $record->set($this->referenceField, $this->reference, false);
+        }
+
+        $this->data[$key] = $record;
+    }
+
+    public function add($record, $key = null)
+    {
+        if (isset($this->referenceField)) {
+            $value = $this->reference->get($this->relation->getLocalFieldName());
+
+            if ($value !== null) {
+                $record->set($this->referenceField, $value, false);
+            } else {
+                $record->set($this->referenceField, $this->reference, false);
+            }
+        }
+        /**
+         * for some weird reason in_array cannot be used here (php bug ?)
+         *
+         * if used it results in fatal error : [ nesting level too deep ]
+         */
+        foreach ($this->data as $val) {
+            if ($val === $record) {
+                return false;
+            }
+        }
+
+        if (isset($key)) {
+            if (isset($this->data[$key])) {
+                return false;
+            }
+            $this->data[$key] = $record;
+            return true;
+        }
+
+        if (isset($this->keyColumn)) {
+            $value = $record->get($this->keyColumn);
+            if ($value === null) {
+                throw new IPF_ORM_Exception("Couldn't create collection index. Record field '".$this->keyColumn."' was null.");
+            }
+            $this->data[$value] = $record;
+        } else {
+            $this->data[] = $record;
+        }
+
+        return true;
+    }
+    
+    public function merge(IPF_ORM_Collection $coll)
+    {
+        $localBase = $this->getTable()->getComponentName();
+        $otherBase = $coll->getTable()->getComponentName();
+        
+        if ($otherBase != $localBase && !is_subclass_of($otherBase, $localBase) ) {
+            throw new IPF_ORM_Exception("Can't merge collections with incompatible record types");
+        }
+        
+        foreach ($coll->getData() as $record) {
+            $this->add($record);
+        }
+        
+        return $this;
+    }
+
+    public function loadRelated($name = null)
+    {
+        $list = array();
+        $query   = new IPF_ORM_Query($this->_table->getConnection());
+
+        if ( ! isset($name)) {
+            foreach ($this->data as $record) {
+                $value = $record->getIncremented();
+                if ($value !== null) {
+                    $list[] = $value;
+                }
+            }
+            $query->from($this->_table->getComponentName());
+            $query->where($this->_table->getComponentName() . '.id IN (' . substr(str_repeat("?, ", count($list)),0,-2) . ')');
+
+            return $query;
+        }
+
+        $rel     = $this->_table->getRelation($name);
+
+        if ($rel instanceof IPF_ORM_Relation_LocalKey || $rel instanceof IPF_ORM_Relation_ForeignKey) {
+            foreach ($this->data as $record) {
+                $list[] = $record[$rel->getLocal()];
+            }
+        } else {
+            foreach ($this->data as $record) {
+                $value = $record->getIncremented();
+                if ($value !== null) {
+                    $list[] = $value;
+                }
+            }
+        }
+
+        $dql     = $rel->getRelationDql(count($list), 'collection');
+
+        $coll    = $query->query($dql, $list);
+
+        $this->populateRelated($name, $coll);
+    }
+
+    public function populateRelated($name, IPF_ORM_Collection $coll)
+    {
+        $rel     = $this->_table->getRelation($name);
+        $table   = $rel->getTable();
+        $foreign = $rel->getForeign();
+        $local   = $rel->getLocal();
+
+        if ($rel instanceof IPF_ORM_Relation_LocalKey) {
+            foreach ($this->data as $key => $record) {
+                foreach ($coll as $k => $related) {
+                    if ($related[$foreign] == $record[$local]) {
+                        $this->data[$key]->setRelated($name, $related);
+                    }
+                }
+            }
+        } elseif ($rel instanceof IPF_ORM_Relation_ForeignKey) {
+            foreach ($this->data as $key => $record) {
+                if ( ! $record->exists()) {
+                    continue;
+                }
+                $sub = new IPF_ORM_Collection($table);
+
+                foreach ($coll as $k => $related) {
+                    if ($related[$foreign] == $record[$local]) {
+                        $sub->add($related);
+                        $coll->remove($k);
+                    }
+                }
+
+                $this->data[$key]->setRelated($name, $sub);
+            }
+        } elseif ($rel instanceof IPF_ORM_Relation_Association) {
+            $identifier = $this->_table->getIdentifier();
+            $asf        = $rel->getAssociationFactory();
+            $name       = $table->getComponentName();
+
+            foreach ($this->data as $key => $record) {
+                if ( ! $record->exists()) {
+                    continue;
+                }
+                $sub = new IPF_ORM_Collection($table);
+                foreach ($coll as $k => $related) {
+                    if ($related->get($local) == $record[$identifier]) {
+                        $sub->add($related->get($name));
+                    }
+                }
+                $this->data[$key]->setRelated($name, $sub);
+
+            }
+        }
+    }
+
+    public function getNormalIterator()
+    {
+        return new IPF_ORM_Collection_Iterator_Normal($this);
+    }
+
+    public function takeSnapshot()
+    {
+        $this->_snapshot = $this->data;
+        
+        return $this;
+    }
+
+    public function getSnapshot()
+    {
+        return $this->_snapshot;
+    }
+
+    public function processDiff() 
+    {
+        foreach (array_udiff($this->_snapshot, $this->data, array($this, "compareRecords")) as $record) {
+            $record->delete();
+        }
+
+        return $this;
+    }
+
+    public function toArray($deep = false, $prefixKey = false)
+    {
+        $data = array();
+        foreach ($this as $key => $record) {
+            
+            $key = $prefixKey ? get_class($record) . '_' .$key:$key;
+            
+            $data[$key] = $record->toArray($deep, $prefixKey);
+        }
+        
+        return $data;
+    }
+
+    public function fromArray($array, $deep = true)
+    {
+        $data = array();
+        foreach ($array as $rowKey => $row) {
+            $this[$rowKey]->fromArray($row, $deep);
+        }
+    }
+
+    public function synchronizeWithArray(array $array)
+    {
+        foreach ($this as $key => $record) {
+            if (isset($array[$key])) {
+                $record->synchronizeWithArray($array[$key]);
+                unset($array[$key]);
+            } else {
+                // remove records that don't exist in the array
+                $this->remove($key);
+            }
+        }
+        // create new records for each new row in the array
+        foreach ($array as $rowKey => $row) {
+            $this[$rowKey]->fromArray($row);
+        }
+    }
+    public function synchronizeFromArray(array $array)
+    {
+        return $this->synchronizeWithArray($array);
+    }
+
+    public function getDeleteDiff()
+    {
+        return array_udiff($this->_snapshot, $this->data, array($this, 'compareRecords'));
+    }
+
+    public function getInsertDiff()
+    {
+        return array_udiff($this->data, $this->_snapshot, array($this, "compareRecords"));
+    }
+
+    protected function compareRecords($a, $b)
+    {
+        if ($a->getOid() == $b->getOid()) {
+            return 0;
+        }
+        
+        return ($a->getOid() > $b->getOid()) ? 1 : -1;
+    }
+
+    public function save(IPF_ORM_Connection $conn = null)
+    {
+        if ($conn == null) {
+            $conn = $this->_table->getConnection();
+        }
+        
+        $conn->beginInternalTransaction();
+
+        $conn->transaction->addCollection($this);
+
+        $this->processDiff();
+
+        foreach ($this->getData() as $key => $record) {
+            $record->save($conn);
+        }
+
+        $conn->commit();
+
+        return $this;
+    }
+
+    public function delete(IPF_ORM_Connection $conn = null, $clearColl = true)
+    {
+        if ($conn == null) {
+            $conn = $this->_table->getConnection();
+        }
+
+        $conn->beginInternalTransaction();
+        $conn->transaction->addCollection($this);
+
+        foreach ($this as $key => $record) {
+            $record->delete($conn);
+        }
+
+        $conn->commit();
+        
+        if ($clearColl) {
+            $this->clear();
+        }
+        
+        return $this;
+    }
+    
+    public function clear()
+    {
+        $this->data = array();
+    }
+
+    public function free($deep = false)
+    {
+        foreach ($this->getData() as $key => $record) {
+            if ( ! ($record instanceof IPF_ORM_Null)) {
+                $record->free($deep);
+            }
+        }
+
+        $this->data = array();
+
+        if ($this->reference) {
+            $this->reference->free($deep);
+            $this->reference = null;
+        }
+    }
+
+    public function getIterator()
+    {
+        $data = $this->data;
+        return new ArrayIterator($data);
+    }
+
+    public function __toString()
+    {
+        return IPF_ORM_Utils::getCollectionAsString($this);
+    }
+    
+    public function getRelation()
+    {
+        return $this->relation;
+    }
+
+    public function __debugInfo()
+    {
+        $r = array();
+        foreach ($this->data as $item)
+            $r[] = $item;
+        return $r;
+    }
+}
+
diff --git a/src/orm/configurable.php b/src/orm/configurable.php
new file mode 100644 (file)
index 0000000..a19ad8f
--- /dev/null
@@ -0,0 +1,180 @@
+<?php
+
+abstract class IPF_ORM_Configurable
+{
+    protected $attributes = array();
+    protected $parent;
+    protected $_params = array();
+
+    public function getAttributeFromString($stringAttributeName)
+    {
+      if (is_string($stringAttributeName)) {
+          $upper = strtoupper($stringAttributeName);
+          $const = 'IPF_ORM::ATTR_' . $upper; 
+          if (defined($const)) {
+              return constant($const);
+          } else {
+              throw new IPF_ORM_Exception('Unknown attribute: "' . $stringAttributeName . '"');
+          }
+      } else {
+        return false;
+      }
+    }
+
+    public function getAttributeValueFromString($stringAttributeName, $stringAttributeValueName)
+    {
+        $const = 'IPF_ORM::' . strtoupper($stringAttributeName) . '_' . strtoupper($stringAttributeValueName);
+        if (defined($const)) {
+            return constant($const);
+        } else {
+            throw new IPF_ORM_Exception('Unknown attribute value: "' . $value . '"');
+        }
+    }
+
+    public function setAttribute($attribute, $value)
+    {
+        if (is_string($attribute)) {
+            $stringAttribute = $attribute;
+            $attribute = $this->getAttributeFromString($attribute);
+            $this->_state = $attribute;
+        }
+
+        if (is_string($value) && isset($stringAttribute)) {
+            $value = $this->getAttributeValueFromString($stringAttribute, $value);
+        }
+
+        switch ($attribute) {
+            case IPF_ORM::ATTR_COLL_KEY:
+                if ( ! ($this instanceof IPF_ORM_Table)) {
+                    throw new IPF_ORM_Exception("This attribute can only be set at table level.");
+                }
+                if ($value !== null && ! $this->hasField($value)) {
+                    throw new IPF_ORM_Exception("Couldn't set collection key attribute. No such field '$value'.");
+                }
+                break;
+            case IPF_ORM::ATTR_CACHE:
+            case IPF_ORM::ATTR_RESULT_CACHE:
+            case IPF_ORM::ATTR_QUERY_CACHE:
+                if ($value !== null) {
+                    if ( ! ($value instanceof IPF_ORM_Cache_Interface)) {
+                        throw new IPF_ORM_Exception('Cache driver should implement IPF_ORM_Cache_Interface');
+                    }
+                }
+                break;
+            case IPF_ORM::ATTR_PORTABILITY:
+            case IPF_ORM::ATTR_DEFAULT_TABLE_TYPE:
+            case IPF_ORM::ATTR_EMULATE_DATABASE:
+            case IPF_ORM::ATTR_USE_NATIVE_ENUM:
+            case IPF_ORM::ATTR_EXPORT:
+            case IPF_ORM::ATTR_DECIMAL_PLACES:
+            case IPF_ORM::ATTR_LOAD_REFERENCES:
+            case IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE:
+            case IPF_ORM::ATTR_MODEL_LOADING:
+            case IPF_ORM::ATTR_RESULT_CACHE_LIFESPAN:
+            case IPF_ORM::ATTR_QUERY_CACHE_LIFESPAN:
+            case IPF_ORM::ATTR_RECURSIVE_MERGE_FIXTURES;
+            case IPF_ORM::ATTR_SINGULARIZE_IMPORT;
+
+                break;
+            case IPF_ORM::ATTR_FIELD_CASE:
+                if ($value != 0 && $value != CASE_LOWER && $value != CASE_UPPER)
+                    throw new IPF_ORM_Exception('Field case attribute should be either 0, CASE_LOWER or CASE_UPPER constant.');
+                break;
+            default:
+                throw new IPF_ORM_Exception("Unknown attribute.");
+        }
+
+        $this->attributes[$attribute] = $value;
+    }
+
+    public function getParams($namespace = null)
+    {
+        if ($namespace == null) {
+            $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE);
+        }
+
+        if (!isset($this->_params[$namespace])) {
+            return null;
+        }
+
+        return $this->_params[$namespace];
+    }
+    
+    public function getParamNamespaces()
+    {
+        return array_keys($this->_params);
+    }
+
+    public function setParam($name, $value, $namespace = null) 
+    {
+        if ($namespace == null) {
+            $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE);
+        }
+
+        $this->_params[$namespace][$name] = $value;
+
+        return $this;
+    }
+    
+    public function getParam($name, $namespace = null) 
+    {
+        if ($namespace == null) {
+            $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE);
+        }
+
+        if (!isset($this->_params[$name])) {
+            if (isset($this->parent)) {
+                return $this->parent->getParam($name, $namespace);
+            }
+            return null;
+        }
+        
+        return $this->_params[$namespace][$name];
+    }
+
+    public function getAttribute($attribute)
+    {
+        if (is_string($attribute)) {
+            $upper = strtoupper($attribute);
+
+            $const = 'IPF_ORM::ATTR_' . $upper; 
+
+            if (defined($const)) {
+                $attribute = constant($const);
+                $this->_state = $attribute;
+            } else {
+                throw new IPF_ORM_Exception('Unknown attribute: "' . $attribute . '"');
+            }
+        }
+
+        $attribute = (int) $attribute;
+
+        if ($attribute < 0) {
+            throw new IPF_ORM_Exception('Unknown attribute.');
+        }
+
+        if (isset($this->attributes[$attribute])) {
+            return $this->attributes[$attribute];
+        }
+        
+        if (isset($this->parent)) {
+            return $this->parent->getAttribute($attribute);
+        }
+        return null;
+    }
+
+    public function getAttributes()
+    {
+        return $this->attributes;
+    }
+
+    public function setParent(IPF_ORM_Configurable $component)
+    {
+        $this->parent = $component;
+    }
+
+    public function getParent()
+    {
+        return $this->parent;
+    }
+}
diff --git a/src/orm/connection.php b/src/orm/connection.php
new file mode 100644 (file)
index 0000000..7fddf02
--- /dev/null
@@ -0,0 +1,646 @@
+<?php
+
+abstract class IPF_ORM_Connection extends IPF_ORM_Configurable implements Countable, IteratorAggregate
+{
+    protected $dbh;
+    protected $tables           = array();
+    protected $_name = 0;
+    protected $driverName;
+    protected $pendingAttributes  = array();
+
+    private $modules = array('transaction' => false,
+                             'expression'  => false,
+                             'export'      => false,
+                             'unitOfWork'  => false,
+                             );
+
+    protected $properties = array('sql_comments'        => array(array('start' => '--', 'end' => "\n", 'escape' => false),
+                                                                 array('start' => '/*', 'end' => '*/', 'escape' => false)),
+                                  'identifier_quoting'  => '"',
+                                  'string_quoting'      => array('start' => "'",
+                                                                 'end' => "'",
+                                                                 'escape' => false,
+                                                                 'escape_pattern' => false),
+                                  'wildcards'           => array('%', '_'),
+                                  'varchar_max_length'  => 255,
+                                  );
+
+    protected $options    = array();
+    private static $availableDrivers    = array(
+                                        'Mysql',
+                                        'Pgsql',
+                                        'Oracle',
+                                        'Informix',
+                                        'Mssql',
+                                        'Sqlite',
+                                        'Firebird'
+                                        );
+    protected $_count = 0;
+
+    public $dbListeners = array();
+
+    public function __construct(IPF_ORM_Manager $manager, $pdo, $user = null, $pass = null)
+    {
+        $this->dbh = $pdo;
+
+        $this->setParent($manager);
+        $this->dbListeners = $manager->dbListeners;
+
+        $this->setAttribute(IPF_ORM::ATTR_CASE, IPF_ORM::CASE_NATURAL);
+        $this->setAttribute(IPF_ORM::ATTR_ERRMODE, IPF_ORM::ERRMODE_EXCEPTION);
+
+        $this->notifyDBListeners('onOpen', $this);
+    }
+
+    public function getOptions()
+    {
+      return $this->options;
+    }
+
+    public function getOption($option)
+    {
+        if (isset($this->options[$option])) {
+            return $this->options[$option];
+        }
+    }
+
+    public function setOption($option, $value)
+    {
+      return $this->options[$option] = $value;
+    }
+
+    public function getAttribute($attribute)
+    {
+        if (is_string($attribute)) {
+            $stringAttribute = $attribute;
+            $attribute = $this->getAttributeFromString($attribute);
+        }
+
+        if ($attribute >= 100) {
+            if ( ! isset($this->attributes[$attribute])) {
+                return parent::getAttribute($attribute);
+            }
+            return $this->attributes[$attribute];
+        }
+
+        try {
+            return $this->dbh->getAttribute($attribute);
+        } catch (Exception $e) {
+            throw new IPF_ORM_Exception('Attribute ' . $attribute . ' not found.');
+        }
+    }
+
+    public static function getAvailableDrivers()
+    {
+        return PDO::getAvailableDrivers();
+    }
+
+    public function setAttribute($attribute, $value)
+    {
+        if (is_string($attribute)) {
+            $attributeString = $attribute;
+            $attribute = parent::getAttributeFromString($attribute);
+        }
+
+        if (is_string($value) && isset($attributeString)) {
+            $value = parent::getAttributeValueFromString($attributeString, $value);
+        }
+
+        if ($attribute >= 100) {
+            parent::setAttribute($attribute, $value);
+        } else {
+            $this->dbh->setAttribute($attribute, $value);
+        }
+
+        return $this;
+    }
+
+    public function getName()
+    {
+        return $this->_name;
+    }
+
+    public function getDriverName()
+    {
+        return $this->driverName;
+    }
+
+    public function __get($name)
+    {
+        if (isset($this->properties[$name])) {
+            return $this->properties[$name];
+        }
+
+        if ( ! isset($this->modules[$name])) {
+            throw new IPF_ORM_Exception('Unknown module / property ' . $name);
+        }
+        if ($this->modules[$name] === false) {
+            switch ($name) {
+                case 'unitOfWork':
+                    $this->modules[$name] = new IPF_ORM_Connection_UnitOfWork($this);
+                    break;
+                default:
+                    $class = 'IPF_ORM_' . ucwords($name) . '_' . $this->getDriverName();
+                    $this->modules[$name] = new $class($this);
+                }
+        }
+
+        return $this->modules[$name];
+    }
+
+    public function getManager()
+    {
+        return $this->getParent();
+    }
+
+    public function getDbh()
+    {
+        return $this->dbh;
+    }
+
+    public function incrementQueryCount() 
+    {
+        $this->_count++;
+    }
+
+    public function replace(IPF_ORM_Table $table, array $fields, array $keys)
+    {
+        if (empty($keys)) {
+            throw new IPF_ORM_Exception('Not specified which fields are keys');
+        }
+        $identifier = (array) $table->getIdentifier();
+        $condition = array();
+
+        foreach ($fields as $fieldName => $value) {
+            if (in_array($fieldName, $keys)) {
+                if ($value !== null) {
+                    $condition[] = $table->getColumnName($fieldName) . ' = ?';
+                    $conditionValues[] = $value;
+                }
+            }
+        }
+
+        $affectedRows = 0;
+        if ( ! empty($condition) && ! empty($conditionValues)) {
+            $query = 'DELETE FROM ' . $this->quoteIdentifier($table->getTableName())
+                    . ' WHERE ' . implode(' AND ', $condition);
+
+            $affectedRows = $this->exec($query, $conditionValues);
+        }
+
+        $this->insert($table, $fields);
+
+        $affectedRows++;
+
+        return $affectedRows;
+    }
+
+    public function delete(IPF_ORM_Table $table, array $identifier)
+    {
+        $tmp = array();
+
+        foreach (array_keys($identifier) as $id) {
+            $tmp[] = $this->quoteIdentifier($table->getColumnName($id)) . ' = ?';
+        }
+
+        $query = 'DELETE FROM '
+               . $this->quoteIdentifier($table->getTableName())
+               . ' WHERE ' . implode(' AND ', $tmp);
+        
+        return $this->exec($query, array_values($identifier));
+    }
+
+    public function update(IPF_ORM_Table $table, array $fields, array $identifier)
+    {
+        if (empty($fields)) {
+            return false;
+        }
+
+        $set = array();
+        foreach ($fields as $fieldName => $value) {
+            if ($value instanceof IPF_ORM_Expression) {
+                $set[] = $this->quoteIdentifier($table->getColumnName($fieldName)) . ' = ' . $value->getSql();
+                unset($fields[$fieldName]);
+            } else {
+                $set[] = $this->quoteIdentifier($table->getColumnName($fieldName)) . ' = ?';
+            }
+        }
+
+        $params = array_merge(array_values($fields), array_values($identifier));
+
+        $sql  = 'UPDATE ' . $this->quoteIdentifier($table->getTableName())
+              . ' SET ' . implode(', ', $set)
+              . ' WHERE ' . implode(' = ? AND ', $table->getIdentifierColumnNames())
+              . ' = ?';
+          
+        return $this->exec($sql, $params);
+    }
+
+    public function insert(IPF_ORM_Table $table, array $fields)
+    {
+        $tableName = $table->getTableName();
+
+        // column names are specified as array keys
+        $cols = array();
+        // the query VALUES will contain either expresions (eg 'NOW()') or ?
+        $a = array();
+        foreach ($fields as $fieldName => $value) {
+            $cols[] = $this->quoteIdentifier($table->getColumnName($fieldName));
+            if ($value instanceof IPF_ORM_Expression) {
+                $a[] = $value->getSql();
+                unset($fields[$fieldName]);
+            } else {
+                $a[] = '?';
+            }
+        }
+
+        // build the statement
+        $query = 'INSERT INTO ' . $this->quoteIdentifier($tableName)
+                . ' (' . implode(', ', $cols) . ')'
+                . ' VALUES (' . implode(', ', $a) . ')';
+
+        return $this->exec($query, array_values($fields));
+    }
+
+    public abstract function quoteIdentifier($str);
+
+    public function convertBooleans($item)
+    {
+        if (is_array($item)) {
+            foreach ($item as $k => $value) {
+                if (is_bool($value)) {
+                    $item[$k] = (int) $value;
+                }
+            }
+        } else {
+            if (is_bool($item)) {
+                $item = (int) $item;
+            }
+        }
+        return $item;
+    }
+
+    public function quote($input, $type=null)
+    {
+        if ($type === null)
+            $type = gettype($input);
+
+        switch ($type) {
+            case 'integer':
+            case 'enum':
+            case 'boolean':
+            case 'double':
+            case 'float':
+            case 'bool':
+            case 'decimal':
+            case 'int':
+                return $input;
+            case 'array':
+            case 'object':
+                $input = serialize($input);
+            case 'date':
+            case 'time':
+            case 'timestamp':
+            case 'string':
+            case 'char':
+            case 'varchar':
+            case 'text':
+            case 'gzip':
+            case 'blob':
+            case 'clob':
+                return $this->getDbh()->quote($input);
+            default:
+                throw new IPF_ORM_Exception('Unsupported type \''.$type.'\'.');
+        }
+    }
+
+    public function escapePattern($text)
+    {
+        $q = $this->string_quoting['escape_pattern'];
+        $text = str_replace($q, $q . $q, $text);
+        foreach ($this->wildcards as $wildcard) {
+            $text = str_replace($wildcard, $q . $wildcard, $text);
+        }
+        return $text;
+    }
+
+    public function setDateFormat($format = null)
+    {
+    }
+
+    public function fetchAll($statement, array $params = array()) 
+    {
+        return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_ASSOC);
+    }
+
+    public function fetchOne($statement, array $params = array(), $colnum = 0) 
+    {
+        return $this->execute($statement, $params)->fetchColumn($colnum);
+    }
+
+    public function fetchRow($statement, array $params = array()) 
+    {
+        return $this->execute($statement, $params)->fetch(IPF_ORM::FETCH_ASSOC);
+    }
+
+    public function fetchArray($statement, array $params = array()) 
+    {
+        return $this->execute($statement, $params)->fetch(IPF_ORM::FETCH_NUM);
+    }
+
+    public function fetchColumn($statement, array $params = array(), $colnum = 0) 
+    {
+        return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_COLUMN, $colnum);
+    }
+
+    public function fetchAssoc($statement, array $params = array()) 
+    {
+        return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_ASSOC);
+    }
+
+    public function fetchBoth($statement, array $params = array()) 
+    {
+        return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_BOTH);
+    }
+
+    public function query($query, array $params = array(), $hydrationMode = null)
+    {
+        $parser = new IPF_ORM_Query($this);
+        $res = $parser->query($query, $params, $hydrationMode);
+        $parser->free();
+
+        return $res;
+    }
+
+    public function prepare($statement)
+    {
+        try {
+            $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_PREPARE, $statement);
+
+            $this->notifyDBListeners('prePrepare', $event);
+
+            $stmt = false;
+
+            if ( ! $event->skipOperation) {
+                $stmt = $this->dbh->prepare($statement);
+            }
+
+            $this->notifyDBListeners('postPrepare', $event);
+
+            return new IPF_ORM_Connection_Statement($this, $stmt);
+        } catch (IPF_ORM_Exception_Adapter $e) {
+        } catch (PDOException $e) {
+        }
+
+        $this->rethrowException($e, $this);
+    }
+
+    public function queryOne($query, array $params = array()) 
+    {
+        $parser = new IPF_ORM_Query($this);
+
+        $coll = $parser->query($query, $params);
+        if ( ! $coll->contains(0)) {
+            return false;
+        }
+        return $coll[0];
+    }
+
+    public function select($query, $limit = 0, $offset = 0)
+    {
+        if ($limit > 0 || $offset > 0) {
+            $query = $this->modifyLimitQuery($query, $limit, $offset);
+        }
+        return $this->execute($query);
+    }
+
+    public function standaloneQuery($query, $params = array())
+    {
+        return $this->execute($query, $params);
+    }
+
+    public function execute($query, array $params = array())
+    {
+        try {
+            if ( ! empty($params)) {
+                $stmt = $this->prepare($query);
+                $stmt->execute($params);
+
+                return $stmt;
+            } else {
+                $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_QUERY, $query, $params);
+
+                $this->notifyDBListeners('preQuery', $event);
+
+                if ( ! $event->skipOperation) {
+                    $stmt = $this->dbh->query($query);
+                    $this->_count++;
+                }
+                $this->notifyDBListeners('postQuery', $event);
+
+                return $stmt;
+            }
+        } catch (IPF_ORM_Exception_Adapter $e) {
+        } catch (PDOException $e) {
+        }
+
+        $this->rethrowException($e, $this);
+    }
+
+    public function exec($query, array $params = array())
+    {
+        try {
+            if ( ! empty($params)) {
+                $stmt = $this->prepare($query);
+                $stmt->execute($params);
+
+                return $stmt->rowCount();
+            } else {
+                $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_EXEC, $query, $params);
+
+                $this->notifyDBListeners('preExec', $event);
+                if ( ! $event->skipOperation) {
+                    $count = $this->dbh->exec($query);
+
+                    $this->_count++;
+                }
+                $this->notifyDBListeners('postExec', $event);
+
+                return $count;
+            }
+        } catch (IPF_ORM_Exception_Adapter $e) {
+        } catch (PDOException $e) { }
+
+        $this->rethrowException($e, $this);
+    }
+
+    public function rethrowException(Exception $e, $invoker)
+    {
+        $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_ERROR);
+        $this->notifyDBListeners('onError', $event);
+
+        $name = 'IPF_ORM_Exception_' . $this->driverName;
+        $exc  = new $name($e->getMessage(), (int) $e->getCode());
+        if (!is_array($e->errorInfo)) {
+            $e->errorInfo = array(null, null, null, null);
+        }
+        $exc->processErrorInfo($e->errorInfo);
+        throw $exc;
+    }
+
+    public function hasTable($name)
+    {
+        return isset($this->tables[$name]);
+    }
+
+    public function getTable($name)
+    {
+        if (!isset($this->tables[$name]))
+            $this->tables[$name] = new IPF_ORM_Table($name, $this);
+        return $this->tables[$name];
+    }
+
+    public function getTables()
+    {
+        return $this->tables;
+    }
+
+    public function getIterator()
+    {
+        return new ArrayIterator($this->tables);
+    }
+
+    public function count()
+    {
+        return $this->_count;
+    }
+
+    public function addTable(IPF_ORM_Table $table)
+    {
+        $name = $table->getComponentName();
+
+        if (isset($this->tables[$name])) {
+            return false;
+        }
+        $this->tables[$name] = $table;
+        return true;
+    }
+
+    public function create($name)
+    {
+        return $this->getTable($name)->create();
+    }
+    
+    public function createQuery()
+    {
+        return new IPF_ORM_Query($this);
+    }
+
+    public function flush()
+    {
+        $this->beginInternalTransaction();
+        $this->unitOfWork->saveAll();
+        $this->commit();
+    }
+
+    public function clear()
+    {
+        foreach ($this->tables as $k => $table) {
+            $table->getRepository()->evictAll();
+            $table->clear();
+        }
+    }
+
+    public function evictTables()
+    {
+        $this->tables = array();
+        $this->exported = array();
+    }
+
+    public function getTransactionLevel()
+    {
+        return $this->transaction->getTransactionLevel();
+    }
+
+    public function errorCode()
+    {
+        return $this->dbh->errorCode();
+    }
+
+    public function errorInfo()
+    {
+        return $this->dbh->errorInfo();
+    }
+    
+    public function getCacheDriver()
+    {
+        return $this->getResultCacheDriver();
+    }
+    
+    public function getResultCacheDriver()
+    {
+        if ( ! $this->getAttribute(IPF_ORM::ATTR_RESULT_CACHE)) {
+            throw new IPF_ORM_Exception('Result Cache driver not initialized.');
+        }
+
+        return $this->getAttribute(IPF_ORM::ATTR_RESULT_CACHE);
+    }
+    
+    public function getQueryCacheDriver()
+    {
+        if ( ! $this->getAttribute(IPF_ORM::ATTR_QUERY_CACHE)) {
+            throw new IPF_ORM_Exception('Query Cache driver not initialized.');
+        }
+
+        return $this->getAttribute(IPF_ORM::ATTR_QUERY_CACHE);
+    }
+
+    public function lastInsertId()
+    {
+        return $this->getDbh()->lastInsertId();
+    }
+
+    public function beginTransaction($savepoint = null)
+    {
+        return $this->transaction->beginTransaction($savepoint);
+    }
+    
+    public function beginInternalTransaction($savepoint = null)
+    {
+        return $this->transaction->beginInternalTransaction($savepoint);
+    }
+
+    public function commit($savepoint = null)
+    {
+        return $this->transaction->commit($savepoint);
+    }
+
+    public function rollback($savepoint = null)
+    {
+        $this->transaction->rollback($savepoint);
+    }
+
+    public function modifyLimitQuery($query, $limit = false, $offset = false, $isManip = false)
+    {
+        return $query;
+    }
+    
+    public function modifyLimitSubquery(IPF_ORM_Table $rootTable, $query, $limit = false,
+            $offset = false, $isManip = false)
+    {
+        return $this->modifyLimitQuery($query, $limit, $offset, $isManip);
+    }
+
+    public function __toString()
+    {
+        return IPF_ORM_Utils::getConnectionAsString($this);
+    }
+
+    public function notifyDBListeners($method, $event)
+    {
+        foreach ($this->dbListeners as $listener)
+            if (is_callable(array($listener, $method)))
+                $listener->$method($event);
+    }
+}
+
diff --git a/src/orm/connection/module.php b/src/orm/connection/module.php
new file mode 100644 (file)
index 0000000..b894e2d
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+class IPF_ORM_Connection_Module
+{
+    protected $conn;
+    protected $moduleName;
+
+    public function __construct($conn = null)
+    {
+        if ( ! ($conn instanceof IPF_ORM_Connection)) {
+            $conn = IPF_ORM_Manager::connection();
+        }
+        $this->conn = $conn;
+
+        $e = explode('_', get_class($this));
+
+        $this->moduleName = $e[1];
+    }
+
+    public function getConnection()
+    {
+        return $this->conn;
+    }
+
+    public function getModuleName()
+    {
+        return $this->moduleName;
+    }
+}
diff --git a/src/orm/connection/mysql.php b/src/orm/connection/mysql.php
new file mode 100644 (file)
index 0000000..4195f87
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+
+class IPF_ORM_Connection_Mysql extends IPF_ORM_Connection
+{
+    protected $driverName = 'Mysql';
+
+    protected static $keywords = array(
+        'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC',
+        'ASENSITIVE', 'BEFORE', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB',
+        'BOTH', 'BY', 'BIT', 'CALL', 'CASCADE', 'CASE', 'CHANGE',
+        'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN',
+        'CONDITION', 'CONNECTION', 'CONSTRAINT', 'CONTINUE',
+        'CONVERT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME',
+        'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE',
+        'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE',
+        'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT',
+        'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC',
+        'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL',
+        'EACH', 'ELSE', 'ELSEIF', 'ENCLOSED', 'ESCAPED', 'EXISTS',
+        'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FLOAT', 'FLOAT4',
+        'FLOAT8', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT',
+        'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY',
+        'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF',
+        'IGNORE', 'IN', 'INDEX', 'INFILE', 'INNER', 'INOUT',
+        'INSENSITIVE', 'INSERT', 'INT', 'INT1', 'INT2', 'INT3',
+        'INT4', 'INT8', 'INTEGER', 'INTERVAL', 'INTO', 'IS',
+        'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LEADING', 'LEAVE',
+        'LEFT', 'LIKE', 'LIMIT', 'LINES', 'LOAD', 'LOCALTIME',
+        'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT',
+        'LOOP', 'LOW_PRIORITY', 'MATCH', 'MEDIUMBLOB', 'MEDIUMINT',
+        'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND',
+        'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NOT',
+        'NO_WRITE_TO_BINLOG', 'NULL', 'NUMERIC', 'ON', 'OPTIMIZE',
+        'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER',
+        'OUTFILE', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE',
+        'RAID0', 'READ', 'READS', 'REAL', 'REFERENCES', 'REGEXP',
+        'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE',
+        'RESTRICT', 'RETURN', 'REVOKE', 'RIGHT', 'RLIKE', 'SCHEMA',
+        'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE',
+        'SEPARATOR', 'SET', 'SHOW', 'SMALLINT', 'SONAME', 'SPATIAL',
+        'SPECIFIC', 'SQL', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING',
+        'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT',
+        'SSL', 'STARTING', 'STRAIGHT_JOIN', 'TABLE', 'TERMINATED',
+        'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING',
+        'TRIGGER', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK',
+        'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE',
+        'UTC_TIME', 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR',
+        'VARCHARACTER', 'VARYING', 'WHEN', 'WHERE', 'WHILE', 'WITH',
+        'WRITE', 'X509', 'XOR', 'YEAR_MONTH', 'ZEROFILL'
+    );
+
+    public function __construct(IPF_ORM_Manager $manager, $adapter)
+    {
+        $this->attributes[IPF_ORM::ATTR_DEFAULT_TABLE_TYPE] = 'INNODB';
+
+        $this->properties['string_quoting'] = array('start' => "'",
+                                                    'end' => "'",
+                                                    'escape' => '\\',
+                                                    'escape_pattern' => '\\');
+
+        $this->properties['identifier_quoting'] = '`';
+
+        $this->properties['sql_comments'] = array(
+                                            array('start' => '-- ', 'end' => "\n", 'escape' => false),
+                                            array('start' => '#', 'end' => "\n", 'escape' => false),
+                                            array('start' => '/*', 'end' => '*/', 'escape' => false),
+                                            );
+
+        $this->properties['varchar_max_length'] = 255;
+
+        parent::__construct($manager, $adapter);
+    }
+
+    public function quoteIdentifier($str)
+    {
+        $quote = $this->identifier_quoting;
+        $q = array();
+        foreach (explode('.', $str) as $s) {
+            if (in_array(strtoupper($s), self::$keywords))
+                $q[] = $quote . str_replace($quote, $quote . $quote, $s) . $quote;
+            else
+                $q[] = $s;
+        }
+        return implode('.', $q);
+    }
+
+    public function getDatabaseName()
+    {
+        return $this->fetchOne('SELECT DATABASE()');
+    }
+
+    public function replace(IPF_ORM_Table $table, array $fields, array $keys)
+    {
+        if (empty($keys)) {
+            throw new IPF_ORM_Exception('Not specified which fields are keys');
+        }
+        $columns = array();
+        $values = array();
+        $params = array();
+        foreach ($fields as $fieldName => $value) {
+            $columns[] = $table->getColumnName($fieldName);
+            $values[] = '?';
+            $params[] = $value;
+        }
+        $query = 'REPLACE INTO ' . $table->getTableName() . ' (' . implode(',', $columns) . ') VALUES (' . implode(',', $values) . ')';
+        return $this->exec($query, $params);
+    }
+
+    public function modifyLimitQuery($query, $limit = false,$offset = false,$isManip=false)
+    {
+        $limit = (int) $limit;
+        $offset = (int) $offset;
+        
+        if ($limit && $offset) {
+            $query .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+        } elseif ($limit && ! $offset) {
+            $query .= ' LIMIT ' . $limit;
+        } elseif ( ! $limit && $offset) {
+            $query .= ' LIMIT 999999999999 OFFSET ' . $offset;
+        }
+        return $query;
+    }
+}
+
diff --git a/src/orm/connection/statement.php b/src/orm/connection/statement.php
new file mode 100644 (file)
index 0000000..cfea7cc
--- /dev/null
@@ -0,0 +1,186 @@
+<?php
+
+class IPF_ORM_Connection_Statement
+{
+    protected $_conn;
+    protected $_stmt;
+    public function __construct(IPF_ORM_Connection $conn, $stmt)
+    {
+        $this->_conn = $conn;
+        $this->_stmt = $stmt;
+
+        if ($stmt === false) {
+            throw new IPF_ORM_Exception('Unknown statement object given.');
+        }
+    }
+
+    public function getConnection()
+    {
+        return $this->_conn;
+    }
+    public function getStatement()
+    {
+        return $this->_stmt;
+    }
+    public function getQuery()
+    {
+        return $this->_stmt->queryString;
+    }
+
+    public function bindColumn($column, $param, $type = null)
+    {
+        if ($type === null) {
+            return $this->_stmt->bindColumn($column, $param);
+        } else {
+            return $this->_stmt->bindColumn($column, $param, $type);
+        }
+    }
+
+    public function bindValue($param, $value, $type = null)
+    {
+        if ($type === null) {
+            return $this->_stmt->bindValue($param, $value);
+        } else {
+            return $this->_stmt->bindValue($param, $value, $type);
+        }
+    }
+
+    public function bindParam($column, &$variable, $type = null, $length = null, $driverOptions = array())
+    {
+        if ($type === null) {
+            return $this->_stmt->bindParam($column, $variable);
+        } else {
+            return $this->_stmt->bindParam($column, $variable, $type, $length, $driverOptions);
+        }
+    }
+
+    public function closeCursor()
+    {
+        return $this->_stmt->closeCursor();
+    }
+
+    public function columnCount()
+    {
+        return $this->_stmt->columnCount();
+    }
+
+    public function errorCode()
+    {
+        return $this->_stmt->errorCode();
+    }
+
+    public function errorInfo()
+    {
+        return $this->_stmt->errorInfo();
+    }
+
+    public function execute($params = null)
+    {
+        try {
+            $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_EXECUTE, $this->getQuery(), $params);
+            $this->_conn->notifyDBListeners('preStmtExecute', $event);
+
+            $result = true;
+            if ( ! $event->skipOperation) {
+                $result = $this->_stmt->execute($params);
+                $this->_conn->incrementQueryCount();
+            }
+
+            $this->_conn->notifyDBListeners('postStmtExecute', $event);
+
+            return $result;
+        } catch (PDOException $e) {
+        } catch (IPF_ORM_Exception_Adapter $e) {
+        }
+
+        $this->_conn->rethrowException($e, $this);
+
+        return false;
+    }
+
+    public function fetch($fetchMode = IPF_ORM::FETCH_BOTH,
+                          $cursorOrientation = IPF_ORM::FETCH_ORI_NEXT,
+                          $cursorOffset = null)
+    {
+        $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_FETCH, $this->getQuery());
+
+        $event->fetchMode = $fetchMode;
+        $event->cursorOrientation = $cursorOrientation;
+        $event->cursorOffset = $cursorOffset;
+
+        $data = $this->_conn->notifyDBListeners('preFetch', $event);
+
+        if ( ! $event->skipOperation) {
+            $data = $this->_stmt->fetch($fetchMode, $cursorOrientation, $cursorOffset);
+        }
+
+        $this->_conn->notifyDBListeners('postFetch', $event);
+
+        return $data;
+    }
+
+    public function fetchAll($fetchMode = IPF_ORM::FETCH_BOTH,
+                             $columnIndex = null)
+    {
+        $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_FETCHALL, $this->getQuery());
+        $event->fetchMode = $fetchMode;
+        $event->columnIndex = $columnIndex;
+
+        $this->_conn->notifyDBListeners('preFetchAll', $event);
+
+        if ( ! $event->skipOperation) {
+            if ($columnIndex !== null) {
+                $data = $this->_stmt->fetchAll($fetchMode, $columnIndex);
+            } else {
+                $data = $this->_stmt->fetchAll($fetchMode);
+            }
+
+            $event->data = $data;
+        }
+
+        $this->_conn->notifyDBListeners('postFetchAll', $event);
+
+        return $data;
+    }
+
+    public function fetchColumn($columnIndex = 0)
+    {
+        return $this->_stmt->fetchColumn($columnIndex);
+    }
+
+    public function fetchObject($className = 'stdClass', $args = array())
+    {
+        return $this->_stmt->fetchObject($className, $args);
+    }
+
+    public function getAttribute($attribute)
+    {
+        return $this->_stmt->getAttribute($attribute);
+    }
+
+    public function getColumnMeta($column)
+    {
+        return $this->_stmt->getColumnMeta($column);
+    }
+
+    public function nextRowset()
+    {
+        return $this->_stmt->nextRowset();
+    }
+
+    public function rowCount()
+    {
+        return $this->_stmt->rowCount();
+    }
+
+    public function setAttribute($attribute, $value)
+    {
+        return $this->_stmt->setAttribute($attribute, $value);
+    }
+
+    public function setFetchMode($mode, $arg1 = null, $arg2 = null)
+    {
+        return $this->_stmt->setFetchMode($mode, $arg1, $arg2);
+    }
+}
+
diff --git a/src/orm/connection/unitofwork.php b/src/orm/connection/unitofwork.php
new file mode 100644 (file)
index 0000000..301b5d7
--- /dev/null
@@ -0,0 +1,574 @@
+<?php
+
+class IPF_ORM_Connection_UnitOfWork extends IPF_ORM_Connection_Module
+{
+    public function saveGraph(IPF_ORM_Record $record)
+    {
+        $conn = $this->getConnection();
+
+        $state = $record->state();
+        if ($state === IPF_ORM_Record::STATE_LOCKED) {
+            return false;
+        }
+
+        $record->state(IPF_ORM_Record::STATE_LOCKED);
+
+        $conn->beginInternalTransaction();
+        $saveLater = $this->saveRelated($record);
+
+        $record->state($state);
+
+        if ($record->isValid()) {
+            $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_SAVE);
+            $record->preSave($event);
+            $record->getTable()->notifyRecordListeners('preSave', $event);
+            $state = $record->state();
+
+            if ( ! $event->skipOperation) {
+                switch ($state) {
+                    case IPF_ORM_Record::STATE_TDIRTY:
+                        $this->insert($record);
+                        break;
+                    case IPF_ORM_Record::STATE_DIRTY:
+                    case IPF_ORM_Record::STATE_PROXY:
+                        $this->update($record);
+                        break;
+                    case IPF_ORM_Record::STATE_CLEAN:
+                    case IPF_ORM_Record::STATE_TCLEAN:
+                        break;
+                }
+            }
+
+            // NOTE: what about referential integrity issues?
+            foreach ($record->getPendingDeletes() as $pendingDelete) {
+                $pendingDelete->delete();
+            }
+
+            $record->getTable()->notifyRecordListeners('postSave', $event);
+            $record->postSave($event);
+        } else {
+            $conn->transaction->addInvalid($record);
+        }
+
+        $state = $record->state();
+
+        $record->state(IPF_ORM_Record::STATE_LOCKED);
+
+        foreach ($saveLater as $fk) {
+            $alias = $fk->getAlias();
+
+            if ($record->hasReference($alias)) {
+                $obj = $record->$alias;
+                // check that the related object is not an instance of IPF_ORM_Null
+                if ( ! ($obj instanceof IPF_ORM_Null)) {
+                    $obj->save($conn);
+                }
+            }
+        }
+
+        // save the MANY-TO-MANY associations
+        $this->saveAssociations($record);
+
+        $record->state($state);
+        $conn->commit();
+
+        return true;
+    }
+
+    public function save(IPF_ORM_Record $record)
+    {
+        $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_SAVE);
+
+        $record->preSave($event);
+
+        $record->getTable()->notifyRecordListeners('preSave', $event);
+
+        if ( ! $event->skipOperation) {
+            switch ($record->state()) {
+                case IPF_ORM_Record::STATE_TDIRTY:
+                    $this->insert($record);
+                    break;
+                case IPF_ORM_Record::STATE_DIRTY:
+                case IPF_ORM_Record::STATE_PROXY:
+                    $this->update($record);
+                    break;
+                case IPF_ORM_Record::STATE_CLEAN:
+                case IPF_ORM_Record::STATE_TCLEAN:
+                    // do nothing
+                    break;
+            }
+        }
+
+        $record->getTable()->notifyRecordListeners('postSave', $event);
+
+        $record->postSave($event);
+    }
+
+    public function delete(IPF_ORM_Record $record)
+    {
+        $deletions = array();
+        $this->_collectDeletions($record, $deletions);
+        return $this->_executeDeletions($deletions);
+    }
+
+    private function _collectDeletions(IPF_ORM_Record $record, array &$deletions)
+    {
+        if ( ! $record->exists()) {
+            return;
+        }
+
+        $deletions[$record->getOid()] = $record;
+        $this->_cascadeDelete($record, $deletions);
+    }
+
+    private function _executeDeletions(array $deletions)
+    {
+        // collect class names
+        $classNames = array();
+        foreach ($deletions as $record) {
+            $classNames[] = $record->getTable()->getComponentName();
+        }
+        $classNames = array_unique($classNames);
+
+        // order deletes
+        $executionOrder = $this->buildFlushTree($classNames);
+
+        // execute
+        try {
+            $this->conn->beginInternalTransaction();
+
+            for ($i = count($executionOrder) - 1; $i >= 0; $i--) {
+                $className = $executionOrder[$i];
+                $table = $this->conn->getTable($className);
+
+                // collect identifiers
+                $identifierMaps = array();
+                $deletedRecords = array();
+                foreach ($deletions as $oid => $record) {
+                    if ($record->getTable()->getComponentName() == $className) {
+                        $veto = $this->_preDelete($record);
+                        if ( ! $veto) {
+                            $identifierMaps[] = $record->identifier();
+                            $deletedRecords[] = $record;
+                            unset($deletions[$oid]);
+                        }
+                    }
+                }
+
+                if (count($deletedRecords) < 1) {
+                    continue;
+                }
+
+                // extract query parameters (only the identifier values are of interest)
+                $params = array();
+                $columnNames = array();
+                foreach ($identifierMaps as $idMap) {
+                    while (list($fieldName, $value) = each($idMap)) {
+                        $params[] = $value;
+                        $columnNames[] = $table->getColumnName($fieldName);
+                    }
+                }
+                $columnNames = array_unique($columnNames);
+
+                // delete
+                $tableName = $table->getTableName();
+                $sql = "DELETE FROM " . $this->conn->quoteIdentifier($tableName) . " WHERE ";
+
+                if ($table->isIdentifierComposite()) {
+                    $sql .= $this->_buildSqlCompositeKeyCondition($columnNames, count($identifierMaps));
+                    $this->conn->exec($sql, $params);
+                } else {
+                    $sql .= $this->_buildSqlSingleKeyCondition($columnNames, count($params));
+                    $this->conn->exec($sql, $params);
+                }
+
+                // adjust state, remove from identity map and inform postDelete listeners
+                foreach ($deletedRecords as $record) {
+                    $record->state(IPF_ORM_Record::STATE_TCLEAN);
+                    $record->getTable()->removeRecord($record);
+                    $this->_postDelete($record);
+                }
+            }
+
+            $this->conn->commit();
+            // trigger postDelete for records skipped during the deletion (veto!)
+            foreach ($deletions as $skippedRecord) {
+                $this->_postDelete($skippedRecord);
+            }
+
+            return true;
+        } catch (Exception $e) {
+            $this->conn->rollback();
+            throw $e;
+        }
+    }
+
+    private function _buildSqlSingleKeyCondition($columnNames, $numRecords)
+    {
+        $idColumn = $this->conn->quoteIdentifier($columnNames[0]);
+        return implode(' OR ', array_fill(0, $numRecords, "$idColumn = ?"));
+    }
+
+    private function _buildSqlCompositeKeyCondition($columnNames, $numRecords)
+    {
+        $singleCondition = "";
+        foreach ($columnNames as $columnName) {
+            $columnName = $this->conn->quoteIdentifier($columnName);
+            if ($singleCondition === "") {
+                $singleCondition .= "($columnName = ?";
+            } else {
+                $singleCondition .= " AND $columnName = ?";
+            }
+        }
+        $singleCondition .= ")";
+        $fullCondition = implode(' OR ', array_fill(0, $numRecords, $singleCondition));
+
+        return $fullCondition;
+    }
+
+    protected function _cascadeDelete(IPF_ORM_Record $record, array &$deletions)
+    {
+        foreach ($record->getTable()->getRelations() as $relation) {
+             if ($relation->isCascadeDelete()) {
+                 $fieldName = $relation->getAlias();
+                 // if it's a xToOne relation and the related object is already loaded
+                 // we don't need to refresh.
+                 if ( ! ($relation->getType() == IPF_ORM_Relation::ONE && isset($record->$fieldName))) {
+                     $record->refreshRelated($relation->getAlias());
+                 }
+                 $relatedObjects = $record->get($relation->getAlias());
+                 if ($relatedObjects instanceof IPF_ORM_Record && $relatedObjects->exists()
+                        && ! isset($deletions[$relatedObjects->getOid()])) {
+                     $this->_collectDeletions($relatedObjects, $deletions);
+                 } else if ($relatedObjects instanceof IPF_ORM_Collection && count($relatedObjects) > 0) {
+                     // cascade the delete to the other objects
+                     foreach ($relatedObjects as $object) {
+                         if ( ! isset($deletions[$object->getOid()])) {
+                             $this->_collectDeletions($object, $deletions);
+                         }
+                     }
+                 }
+             }
+         }
+     }
+
+    public function saveRelated(IPF_ORM_Record $record)
+    {
+        $saveLater = array();
+        foreach ($record->getReferences() as $k => $v) {
+            $rel = $record->getTable()->getRelation($k);
+
+            $local = $rel->getLocal();
+            $foreign = $rel->getForeign();
+
+            if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
+                $saveLater[$k] = $rel;
+            } else if ($rel instanceof IPF_ORM_Relation_LocalKey) {
+                // ONE-TO-ONE relationship
+                $obj = $record->get($rel->getAlias());
+
+                // Protection against infinite function recursion before attempting to save
+                if ($obj instanceof IPF_ORM_Record && $obj->isModified()) {
+                    $obj->save($this->conn);
+
+                    /** Can this be removed?
+                    $id = array_values($obj->identifier());
+
+                    foreach ((array) $rel->getLocal() as $k => $field) {
+                        $record->set($field, $id[$k]);
+                    }
+                    */
+                }
+            }
+        }
+
+        return $saveLater;
+    }
+
+    public function saveAssociations(IPF_ORM_Record $record)
+    {
+        foreach ($record->getReferences() as $k => $v) {
+            $rel = $record->getTable()->getRelation($k);
+            //print get_class($rel);
+            if ($rel instanceof IPF_ORM_Relation_Association) {
+                $v->save($this->conn);
+
+                $assocTable = $rel->getAssociationTable();
+
+                foreach ($v->getDeleteDiff() as $r) {
+                    $query = 'DELETE FROM ' . $assocTable->getTableName()
+                           . ' WHERE ' . $rel->getForeign() . ' = ?'
+                           . ' AND ' . $rel->getLocal() . ' = ?';
+
+                    $this->conn->execute($query, array($r->getIncremented(), $record->getIncremented()));
+                }
+
+                foreach ($v->getInsertDiff() as $r) {
+                    $assocRecord = $assocTable->create();
+                    $assocRecord->set($assocTable->getFieldName($rel->getForeign()), $r);
+                    $assocRecord->set($assocTable->getFieldName($rel->getLocal()), $record);
+                    $this->saveGraph($assocRecord);
+                }
+            }
+        }
+    }
+
+    private function _preDelete(IPF_ORM_Record $record)
+    {
+        $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_DELETE);
+        $record->preDelete($event);
+        $record->getTable()->notifyRecordListeners('preDelete', $event);
+
+        return $event->skipOperation;
+    }
+
+    private function _postDelete(IPF_ORM_Record $record)
+    {
+        $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_DELETE);
+        $record->postDelete($event);
+        $record->getTable()->notifyRecordListeners('postDelete', $event);
+    }
+
+    public function saveAll()
+    {
+        // get the flush tree
+        $tree = $this->buildFlushTree($this->conn->getTables());
+
+        // save all records
+        foreach ($tree as $name) {
+            $table = $this->conn->getTable($name);
+            foreach ($table->getRepository() as $record) {
+                $this->saveGraph($record);
+            }
+        }
+    }
+
+    public function update(IPF_ORM_Record $record)
+    {
+        $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_UPDATE);
+        $record->preUpdate($event);
+        $table = $record->getTable();
+        $table->notifyRecordListeners('preUpdate', $event);
+
+        if ( ! $event->skipOperation) {
+            $identifier = $record->identifier();
+            $array = $record->getPrepared();
+            $this->conn->update($table, $array, $identifier);
+            $record->assignIdentifier(true);
+        }
+
+        $table->notifyRecordListeners('postUpdate', $event);
+
+        $record->postUpdate($event);
+
+        return true;
+    }
+
+    public function insert(IPF_ORM_Record $record)
+    {
+        // listen the onPreInsert event
+        $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_INSERT);
+        $record->preInsert($event);
+        $table = $record->getTable();
+        $table->notifyRecordListeners('preInsert', $event);
+
+        if ( ! $event->skipOperation) {
+            $this->processSingleInsert($record);
+        }
+
+        $table->addRecord($record);
+        $table->notifyRecordListeners('postInsert', $event);
+        $record->postInsert($event);
+
+        return true;
+    }
+
+    public function processSingleInsert(IPF_ORM_Record $record)
+    {
+        $fields = $record->getPrepared();
+        $table = $record->getTable();
+
+        // Populate fields with a blank array so that a blank records can be inserted
+        if (empty($fields)) {
+            foreach ($table->getFieldNames() as $field) {
+                $fields[$field] = null;
+            }
+        }
+
+        $identifier = (array) $table->getIdentifier();
+
+        $this->conn->insert($table, $fields);
+
+        if (count($identifier) == 1 && $table->getIdentifierType() !== IPF_ORM::IDENTIFIER_NATURAL) {
+            $id = $this->conn->lastInsertId();
+            if (!$id)
+                throw new IPF_ORM_Exception("Couldn't get last insert identifier.");
+
+            $record->assignIdentifier($id);
+        } else {
+            $record->assignIdentifier(true);
+        }
+    }
+
+    public function buildFlushTree(array $tables)
+    {
+        // determine classes to order. only necessary because the $tables param
+        // can contain strings or table objects...
+        $classesToOrder = array();
+        foreach ($tables as $table) {
+            if ( ! ($table instanceof IPF_ORM_Table)) {
+                $table = $this->conn->getTable($table, false);
+            }
+            $classesToOrder[] = $table->getComponentName();
+        }
+        $classesToOrder = array_unique($classesToOrder);
+
+        if (count($classesToOrder) < 2) {
+            return $classesToOrder;
+        }
+
+        // build the correct order
+        $flushList = array();
+        foreach ($classesToOrder as $class) {
+            $table = $this->conn->getTable($class, false);
+            $currentClass = $table->getComponentName();
+
+            $index = array_search($currentClass, $flushList);
+
+            if ($index === false) {
+                //echo "adding $currentClass to flushlist";
+                $flushList[] = $currentClass;
+                $index = max(array_keys($flushList));
+            }
+
+            $rels = $table->getRelations();
+
+            // move all foreignkey relations to the beginning
+            foreach ($rels as $key => $rel) {
+                if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
+                    unset($rels[$key]);
+                    array_unshift($rels, $rel);
+                }
+            }
+
+            foreach ($rels as $rel) {
+                $relatedClassName = $rel->getTable()->getComponentName();
+
+                if ( ! in_array($relatedClassName, $classesToOrder)) {
+                    continue;
+                }
+
+                $relatedCompIndex = array_search($relatedClassName, $flushList);
+                $type = $rel->getType();
+
+                // skip self-referenced relations
+                if ($relatedClassName === $currentClass) {
+                    continue;
+                }
+
+                if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
+                    // the related component needs to come after this component in
+                    // the list (since it holds the fk)
+
+                    if ($relatedCompIndex !== false) {
+                        // the component is already in the list
+                        if ($relatedCompIndex >= $index) {
+                            // it's already in the right place
+                            continue;
+                        }
+
+                        unset($flushList[$index]);
+                        // the related comp has the fk. so put "this" comp immediately
+                        // before it in the list
+                        array_splice($flushList, $relatedCompIndex, 0, $currentClass);
+                        $index = $relatedCompIndex;
+                    } else {
+                        $flushList[] = $relatedClassName;
+                    }
+
+                } else if ($rel instanceof IPF_ORM_Relation_LocalKey) {
+                    // the related component needs to come before the current component
+                    // in the list (since this component holds the fk).
+
+                    if ($relatedCompIndex !== false) {
+                        // already in flush list
+                        if ($relatedCompIndex <= $index) {
+                            // it's in the right place
+                            continue;
+                        }
+
+                        unset($flushList[$relatedCompIndex]);
+                        // "this" comp has the fk. so put the related comp before it
+                        // in the list
+                        array_splice($flushList, $index, 0, $relatedClassName);
+                    } else {
+                        array_unshift($flushList, $relatedClassName);
+                        $index++;
+                    }
+                } else if ($rel instanceof IPF_ORM_Relation_Association) {
+                    // the association class needs to come after both classes
+                    // that are connected through it in the list (since it holds
+                    // both fks)
+
+                    $assocTable = $rel->getAssociationFactory();
+                    $assocClassName = $assocTable->getComponentName();
+
+                    if ($relatedCompIndex !== false) {
+                        unset($flushList[$relatedCompIndex]);
+                    }
+
+                    array_splice($flushList, $index, 0, $relatedClassName);
+                    $index++;
+
+                    $index3 = array_search($assocClassName, $flushList);
+
+                    if ($index3 !== false) {
+                        if ($index3 >= $index) {
+                            continue;
+                        }
+
+                        unset($flushList[$index]);
+                        array_splice($flushList, $index3, 0, $assocClassName);
+                        $index = $relatedCompIndex;
+                    } else {
+                        $flushList[] = $assocClassName;
+                    }
+                }
+            }
+        }
+
+        return array_values($flushList);
+    }
+
+    private function _formatDataSet(IPF_ORM_Record $record)
+    {
+        $table = $record->getTable();
+        $dataSet = array();
+        $component = $table->getComponentName();
+        $array = $record->getPrepared();
+
+        foreach ($table->getColumns() as $columnName => $definition) {
+            if ( ! isset($dataSet[$component])) {
+                $dataSet[$component] = array();
+            }
+
+            $fieldName = $table->getFieldName($columnName);
+            if (isset($definition['primary']) && $definition['primary']) {
+                continue;
+            }
+
+            if ( ! array_key_exists($fieldName, $array)) {
+                continue;
+            }
+
+            if (isset($definition['owner'])) {
+                $dataSet[$definition['owner']][$fieldName] = $array[$fieldName];
+            } else {
+                $dataSet[$component][$fieldName] = $array[$fieldName];
+            }
+        }
+
+        return $dataSet;
+    }
+}
+
diff --git a/src/orm/event.php b/src/orm/event.php
new file mode 100644 (file)
index 0000000..c52fd29
--- /dev/null
@@ -0,0 +1,194 @@
+<?php
+
+class IPF_ORM_Event
+{
+    const CONN_QUERY         = 1;
+    const CONN_EXEC          = 2;
+    const CONN_PREPARE       = 3;
+    const CONN_CONNECT       = 4;
+    const CONN_CLOSE         = 5;
+    const CONN_ERROR         = 6;
+
+    const STMT_EXECUTE       = 10;
+    const STMT_FETCH         = 11;
+    const STMT_FETCHALL      = 12;
+
+    const TX_BEGIN           = 31;
+    const TX_COMMIT          = 32;
+    const TX_ROLLBACK        = 33;
+    const SAVEPOINT_CREATE   = 34;
+    const SAVEPOINT_ROLLBACK = 35;
+    const SAVEPOINT_COMMIT   = 36;
+
+    const HYDRATE            = 40;
+
+    const RECORD_DELETE      = 21;
+    const RECORD_SAVE        = 22;
+    const RECORD_UPDATE      = 23;
+    const RECORD_INSERT      = 24;
+    const RECORD_SERIALIZE   = 25;
+    const RECORD_UNSERIALIZE = 26;
+    const RECORD_DQL_SELECT  = 28;
+    const RECORD_DQL_DELETE  = 27;
+    const RECORD_DQL_UPDATE  = 29;
+
+    protected $_invoker;
+
+    protected $_query;
+
+    protected $_params;
+
+    protected $_code;
+
+    protected $_startedMicrotime;
+
+    protected $_endedMicrotime;
+
+    protected $_options = array();
+
+    public function __construct($invoker, $code, $query = null, $params = array())
+    {
+        $this->_invoker = $invoker;
+        $this->_code    = $code;
+        $this->_query   = $query;
+        $this->_params  = $params;
+    }
+
+    public function getQuery()
+    {
+        return $this->_query;
+    }
+
+    public function getName()
+    {
+        switch ($this->_code) {
+            case self::CONN_QUERY:
+                return 'query';
+            case self::CONN_EXEC:
+                return 'exec';
+            case self::CONN_PREPARE:
+                return 'prepare';
+            case self::CONN_CONNECT:
+                return 'connect';
+            case self::CONN_CLOSE:
+                return 'close';
+            case self::CONN_ERROR:
+                return 'error';
+
+            case self::STMT_EXECUTE:
+                return 'execute';
+            case self::STMT_FETCH:
+                return 'fetch';
+            case self::STMT_FETCHALL:
+                return 'fetch all';
+
+            case self::TX_BEGIN:
+                return 'begin';
+            case self::TX_COMMIT:
+                return 'commit';
+            case self::TX_ROLLBACK:
+                return 'rollback';
+
+            case self::SAVEPOINT_CREATE:
+                return 'create savepoint';
+            case self::SAVEPOINT_ROLLBACK:
+                return 'rollback savepoint';
+            case self::SAVEPOINT_COMMIT:
+                return 'commit savepoint';
+
+            case self::RECORD_DELETE:
+                return 'delete record';
+            case self::RECORD_SAVE:
+                return 'save record';
+            case self::RECORD_UPDATE:
+                return 'update record';
+            case self::RECORD_INSERT:
+                return 'insert record';
+            case self::RECORD_SERIALIZE:
+                return 'serialize record';
+            case self::RECORD_UNSERIALIZE:
+                return 'unserialize record';
+            case self::RECORD_DQL_SELECT:
+                return 'select records';
+            case self::RECORD_DQL_DELETE:
+                return 'delete records';
+            case self::RECORD_DQL_UPDATE:
+                return 'update records';
+        }
+    }
+
+    public function getCode()
+    {
+        return $this->_code;
+    }
+
+    public function __get($option)
+    {
+        if ( ! isset($this->_options[$option])) {
+            return null;
+        }
+
+        return $this->_options[$option];
+    }
+
+    public function skipOperation()
+    {
+        $this->_options['skipOperation'] = true;
+
+        return $this;
+    }
+
+    public function __set($option, $value)
+    {
+        $this->_options[$option] = $value;
+
+        return $this;
+    }
+
+    public function set($option, &$value)
+    {
+        $this->_options[$option] =& $value;
+
+        return $this;
+    }
+
+    public function start()
+    {
+        $this->_startedMicrotime = microtime(true);
+    }
+
+    public function hasEnded()
+    {
+        return ($this->_endedMicrotime != null);
+    }
+
+    public function end()
+    {
+        $this->_endedMicrotime = microtime(true);
+
+        return $this;
+    }
+
+    public function getInvoker()
+    {
+        return $this->_invoker;
+    }
+
+    public function setInvoker($invoker)
+    {
+        $this->_invoker = $invoker;
+    }
+
+    public function getParams()
+    {
+        return $this->_params;
+    }
+
+    public function getElapsedSecs()
+    {
+        if (is_null($this->_endedMicrotime)) {
+            return false;
+        }
+        return ($this->_endedMicrotime - $this->_startedMicrotime);
+    }
+}
diff --git a/src/orm/exception.php b/src/orm/exception.php
new file mode 100644 (file)
index 0000000..d7d84e9
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+
+class IPF_ORM_Exception extends IPF_Exception
+{
+    protected static $_errorMessages = array(
+        IPF_ORM::ERR                    => 'unknown error',
+        IPF_ORM::ERR_ALREADY_EXISTS     => 'already exists',
+        IPF_ORM::ERR_CANNOT_CREATE      => 'can not create',
+        IPF_ORM::ERR_CANNOT_ALTER       => 'can not alter',
+        IPF_ORM::ERR_CANNOT_REPLACE     => 'can not replace',
+        IPF_ORM::ERR_CANNOT_DELETE      => 'can not delete',
+        IPF_ORM::ERR_CANNOT_DROP        => 'can not drop',
+        IPF_ORM::ERR_CONSTRAINT         => 'constraint violation',
+        IPF_ORM::ERR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
+        IPF_ORM::ERR_DIVZERO            => 'division by zero',
+        IPF_ORM::ERR_INVALID            => 'invalid',
+        IPF_ORM::ERR_INVALID_DATE       => 'invalid date or time',
+        IPF_ORM::ERR_INVALID_NUMBER     => 'invalid number',
+        IPF_ORM::ERR_MISMATCH           => 'mismatch',
+        IPF_ORM::ERR_NODBSELECTED       => 'no database selected',
+        IPF_ORM::ERR_NOSUCHFIELD        => 'no such field',
+        IPF_ORM::ERR_NOSUCHTABLE        => 'no such table',
+        IPF_ORM::ERR_NOT_CAPABLE        => 'IPF backend not capable',
+        IPF_ORM::ERR_NOT_FOUND          => 'not found',
+        IPF_ORM::ERR_NOT_LOCKED         => 'not locked',
+        IPF_ORM::ERR_SYNTAX             => 'syntax error',
+        IPF_ORM::ERR_UNSUPPORTED        => 'not supported',
+        IPF_ORM::ERR_VALUE_COUNT_ON_ROW => 'value count on row',
+        IPF_ORM::ERR_INVALID_DSN        => 'invalid DSN',
+        IPF_ORM::ERR_CONNECT_FAILED     => 'connect failed',
+        IPF_ORM::ERR_NEED_MORE_DATA     => 'insufficient data supplied',
+        IPF_ORM::ERR_EXTENSION_NOT_FOUND=> 'extension not found',
+        IPF_ORM::ERR_NOSUCHDB           => 'no such database',
+        IPF_ORM::ERR_ACCESS_VIOLATION   => 'insufficient permissions',
+        IPF_ORM::ERR_LOADMODULE         => 'error while including on demand module',
+        IPF_ORM::ERR_TRUNCATED          => 'truncated',
+        IPF_ORM::ERR_DEADLOCK           => 'deadlock detected',
+    );
+
+    public function errorMessage($value = null)
+    {
+        if (is_null($value)) {
+            return self::$_errorMessages;
+        }
+
+        return isset(self::$_errorMessages[$value]) ?
+           self::$_errorMessages[$value] : self::$_errorMessages[IPF_ORM::ERR];
+    }
+}
+
diff --git a/src/orm/exception/adapter.php b/src/orm/exception/adapter.php
new file mode 100644 (file)
index 0000000..7f8ce00
--- /dev/null
@@ -0,0 +1,3 @@
+<?php
+
+class IPF_ORM_Exception_Adapter extends IPF_ORM_Exception{}
diff --git a/src/orm/exception/connection.php b/src/orm/exception/connection.php
new file mode 100644 (file)
index 0000000..fc43bff
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+
+class IPF_ORM_Exception_Connection extends IPF_ORM_Exception
+{
+    static protected $errorMessages = array(
+                IPF_ORM::ERR                    => 'unknown error',
+                IPF_ORM::ERR_ALREADY_EXISTS     => 'already exists',
+                IPF_ORM::ERR_CANNOT_CREATE      => 'can not create',
+                IPF_ORM::ERR_CANNOT_ALTER       => 'can not alter',
+                IPF_ORM::ERR_CANNOT_REPLACE     => 'can not replace',
+                IPF_ORM::ERR_CANNOT_DELETE      => 'can not delete',
+                IPF_ORM::ERR_CANNOT_DROP        => 'can not drop',
+                IPF_ORM::ERR_CONSTRAINT         => 'constraint violation',
+                IPF_ORM::ERR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
+                IPF_ORM::ERR_DIVZERO            => 'division by zero',
+                IPF_ORM::ERR_INVALID            => 'invalid',
+                IPF_ORM::ERR_INVALID_DATE       => 'invalid date or time',
+                IPF_ORM::ERR_INVALID_NUMBER     => 'invalid number',
+                IPF_ORM::ERR_MISMATCH           => 'mismatch',
+                IPF_ORM::ERR_NODBSELECTED       => 'no database selected',
+                IPF_ORM::ERR_NOSUCHFIELD        => 'no such field',
+                IPF_ORM::ERR_NOSUCHTABLE        => 'no such table',
+                IPF_ORM::ERR_NOT_CAPABLE        => 'IPF_ORM backend not capable',
+                IPF_ORM::ERR_NOT_FOUND          => 'not found',
+                IPF_ORM::ERR_NOT_LOCKED         => 'not locked',
+                IPF_ORM::ERR_SYNTAX             => 'syntax error',
+                IPF_ORM::ERR_UNSUPPORTED        => 'not supported',
+                IPF_ORM::ERR_VALUE_COUNT_ON_ROW => 'value count on row',
+                IPF_ORM::ERR_INVALID_DSN        => 'invalid DSN',
+                IPF_ORM::ERR_CONNECT_FAILED     => 'connect failed',
+                IPF_ORM::ERR_NEED_MORE_DATA     => 'insufficient data supplied',
+                IPF_ORM::ERR_EXTENSION_NOT_FOUND=> 'extension not found',
+                IPF_ORM::ERR_NOSUCHDB           => 'no such database',
+                IPF_ORM::ERR_ACCESS_VIOLATION   => 'insufficient permissions',
+                IPF_ORM::ERR_LOADMODULE         => 'error while including on demand module',
+                IPF_ORM::ERR_TRUNCATED          => 'truncated',
+                IPF_ORM::ERR_DEADLOCK           => 'deadlock detected',
+                );
+
+    protected $portableCode;
+
+    public function getPortableCode()
+    {
+        return $this->portableCode;
+    }
+
+    public function getPortableMessage()
+    {
+        return self::errorMessage($this->portableCode);
+    }
+
+    public function errorMessage($value = null)
+    {
+        return isset(self::$errorMessages[$value]) ?
+           self::$errorMessages[$value] : self::$errorMessages[IPF_ORM::ERR];
+    }
+
+    public function processErrorInfo(array $errorInfo)
+    { }
+}
\ No newline at end of file
diff --git a/src/orm/exception/locator.php b/src/orm/exception/locator.php
new file mode 100644 (file)
index 0000000..d39308f
--- /dev/null
@@ -0,0 +1,3 @@
+<?php
+
+class IPF_ORM_Exception_Locator extends IPF_ORM_Exception_Base{}
diff --git a/src/orm/exception/mysql.php b/src/orm/exception/mysql.php
new file mode 100644 (file)
index 0000000..1d622df
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+class IPF_ORM_Exception_Mysql extends IPF_ORM_Exception_Connection
+{
+    protected static $errorCodeMap = array(
+                                      1004 => IPF_ORM::ERR_CANNOT_CREATE,
+                                      1005 => IPF_ORM::ERR_CANNOT_CREATE,
+                                      1006 => IPF_ORM::ERR_CANNOT_CREATE,
+                                      1007 => IPF_ORM::ERR_ALREADY_EXISTS,
+                                      1008 => IPF_ORM::ERR_CANNOT_DROP,
+                                      1022 => IPF_ORM::ERR_ALREADY_EXISTS,
+                                      1044 => IPF_ORM::ERR_ACCESS_VIOLATION,
+                                      1046 => IPF_ORM::ERR_NODBSELECTED,
+                                      1048 => IPF_ORM::ERR_CONSTRAINT,
+                                      1049 => IPF_ORM::ERR_NOSUCHDB,
+                                      1050 => IPF_ORM::ERR_ALREADY_EXISTS,
+                                      1051 => IPF_ORM::ERR_NOSUCHTABLE,
+                                      1054 => IPF_ORM::ERR_NOSUCHFIELD,
+                                      1061 => IPF_ORM::ERR_ALREADY_EXISTS,
+                                      1062 => IPF_ORM::ERR_ALREADY_EXISTS,
+                                      1064 => IPF_ORM::ERR_SYNTAX,
+                                      1091 => IPF_ORM::ERR_NOT_FOUND,
+                                      1100 => IPF_ORM::ERR_NOT_LOCKED,
+                                      1136 => IPF_ORM::ERR_VALUE_COUNT_ON_ROW,
+                                      1142 => IPF_ORM::ERR_ACCESS_VIOLATION,
+                                      1146 => IPF_ORM::ERR_NOSUCHTABLE,
+                                      1216 => IPF_ORM::ERR_CONSTRAINT,
+                                      1217 => IPF_ORM::ERR_CONSTRAINT,
+                                      1451 => IPF_ORM::ERR_CONSTRAINT,
+                                      );
+    public function processErrorInfo(array $errorInfo)
+    {
+        $code = $errorInfo[1];
+        if (isset(self::$errorCodeMap[$code])) {
+            $this->portableCode = self::$errorCodeMap[$code];
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/orm/exception/profiler.php b/src/orm/exception/profiler.php
new file mode 100644 (file)
index 0000000..5fa9ad5
--- /dev/null
@@ -0,0 +1,3 @@
+<?php
+
+class IPF_ORM_Exception_Profiler extends IPF_ORM_Exception{}
diff --git a/src/orm/exception/validator.php b/src/orm/exception/validator.php
new file mode 100644 (file)
index 0000000..597409b
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+class IPF_ORM_Exception_Validator extends IPF_ORM_Exception
+{
+    private $invalid = array();
+
+    public function __construct(array $invalid)
+    {
+        $this->invalid = $invalid;
+        parent::__construct($this->generateMessage());
+    }
+
+    public function getInvalidRecords()
+    {
+        return $this->invalid;
+    }
+
+    private function generateMessage()
+    {
+        $message = "";
+        foreach ($this->invalid as $record) {
+            $errors = array();
+            foreach ($record->getErrors() as $field => $validators)
+                $errors[] = 'Field "' . $field . '" failed following validators: ' . implode(', ', $validators) . '.';
+            $message .= "Validaton error in class " . get_class($record) . ' (' . implode(' ', $errors) . ') ';
+        }
+        return $message;
+    }
+}
+
diff --git a/src/orm/export.php b/src/orm/export.php
new file mode 100644 (file)
index 0000000..a0cae94
--- /dev/null
@@ -0,0 +1,479 @@
+<?php
+
+class IPF_ORM_Export extends IPF_ORM_Connection_Module
+{
+    protected $valid_default_values = array(
+        'text'      => '',
+        'boolean'   => true,
+        'integer'   => 0,
+        'decimal'   => 0.0,
+        'float'     => 0.0,
+        'double'    => 0.0,
+        'timestamp' => '1970-01-01 00:00:00',
+        'time'      => '00:00:00',
+        'date'      => '1970-01-01',
+        'clob'      => '',
+        'blob'      => '',
+        'string'    => ''
+    );
+
+    protected function getIndexName($name)
+    {
+        return $name . '_idx';
+    }
+
+    public function dropTableSql($table)
+    {
+        return 'DROP TABLE ' . $this->conn->quoteIdentifier($table);
+    }
+
+    public function dropTable($table)
+    {
+        $this->conn->execute($this->dropTableSql($table));
+    }
+
+    public function dropIndex($table, $name)
+    {
+        return $this->conn->exec($this->dropIndexSql($table, $name));
+    }
+
+    public function dropIndexSql($table, $name)
+    {
+        $name = $this->conn->quoteIdentifier($this->getIndexName($name));
+        
+        return 'DROP INDEX ' . $name;
+    }
+
+    public function dropConstraint($table, $name, $primary = false)
+    {
+        $table = $this->conn->quoteIdentifier($table);
+        $name  = $this->conn->quoteIdentifier($name);
+        
+        return $this->conn->exec('ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name);
+    }
+
+    public function dropForeignKey($table, $name)
+    {
+        return $this->dropConstraint($table, $name);
+    }
+
+    public function createConstraint($table, $name, $definition)
+    {
+        $sql = $this->createConstraintSql($table, $name, $definition);
+        
+        return $this->conn->exec($sql);
+    }
+
+    public function createConstraintSql($table, $name, $definition)
+    {
+        $table = $this->conn->quoteIdentifier($table);
+        $name  = $this->conn->quoteIdentifier($this->getIndexName($name));
+        $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $name;
+
+        if (isset($definition['primary']) && $definition['primary']) {
+            $query .= ' PRIMARY KEY';
+        } elseif (isset($definition['unique']) && $definition['unique']) {
+            $query .= ' UNIQUE';
+        }
+
+        $fields = array();
+        foreach (array_keys($definition['fields']) as $field) {
+            $fields[] = $this->conn->quoteIdentifier($field);
+        }
+        $query .= ' ('. implode(', ', $fields) . ')';
+
+        return $query;
+    }
+
+    public function createIndex($table, $name, array $definition)
+    {
+        return $this->conn->execute($this->createIndexSql($table, $name, $definition));
+    }
+
+    public function createIndexSql($table, $name, array $definition)
+    {
+        $table  = $this->conn->quoteIdentifier($table);
+        $name   = $this->conn->quoteIdentifier($name);
+        $type   = '';
+
+        if (isset($definition['type'])) {
+            switch (strtolower($definition['type'])) {
+                case 'unique':
+                    $type = strtoupper($definition['type']) . ' ';
+                break;
+                default:
+                    throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']);
+            }
+        }
+
+        $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table;
+
+        $fields = array();
+        foreach ($definition['fields'] as $field) {
+            $fields[] = $this->conn->quoteIdentifier($field);
+        }
+        $query .= ' (' . implode(', ', $fields) . ')';
+
+        return $query;
+    }    
+
+    public function createForeignKeySql($table, array $definition)
+    {
+        $table = $this->conn->quoteIdentifier($table);
+
+        $query = 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclaration($definition);
+
+        return $query;
+    }
+
+    public function createForeignKey($table, array $definition)
+    {
+        $sql = $this->createForeignKeySql($table, $definition);
+        
+        return $this->conn->execute($sql);
+    }
+
+    public function alterTable($name, array $changes, $check = false)
+    {
+        $sql = $this->alterTableSql($name, $changes, $check);
+        
+        if (is_string($sql) && $sql) {
+            $this->conn->execute($sql);
+        }
+    }
+
+    public function alterTableSql($name, array $changes, $check = false)
+    {
+        throw new IPF_ORM_Exception('Alter table not supported by this driver.');
+    }
+
+    public function getFieldDeclarationList(array $fields)
+    {
+        foreach ($fields as $fieldName => $field) {
+            $query = $this->getDeclaration($fieldName, $field);
+
+            $queryFields[] = $query;
+        }
+        return implode(', ', $queryFields);
+    }
+
+    public function getCheckDeclaration(array $definition)
+    {
+        $constraints = array();
+        foreach ($definition as $field => $def) {
+            if (is_string($def)) {
+                $constraints[] = 'CHECK (' . $def . ')';
+            } else {
+                if (isset($def['min'])) {
+                    $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')';
+                }
+
+                if (isset($def['max'])) {
+                    $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')';
+                }
+            }
+        }
+
+        return implode(', ', $constraints);
+    }
+
+    public function getIndexDeclaration($name, array $definition)
+    {
+        $name   = $this->conn->quoteIdentifier($name);
+        $type   = '';
+
+        if (isset($definition['type'])) {
+            if (strtolower($definition['type']) == 'unique') {
+                $type = strtoupper($definition['type']) . ' ';
+            } else {
+                throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']);
+            }
+        }
+
+        if ( ! isset($definition['fields']) || ! is_array($definition['fields'])) {
+            throw new IPF_ORM_Exception('No index columns given.');
+        }
+
+        $query = $type . 'INDEX ' . $name;
+
+        $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
+
+        return $query;
+    }
+
+    public function getIndexFieldDeclarationList(array $fields)
+    {
+        $ret = array();
+        foreach ($fields as $field => $definition) {
+            if (is_array($definition)) {
+                $ret[] = $this->conn->quoteIdentifier($field);
+            } else {
+                $ret[] = $this->conn->quoteIdentifier($definition);
+            }
+        }
+        return implode(', ', $ret);
+    }
+
+    public function getTemporaryTableQuery()
+    {
+        return 'TEMPORARY';
+    }
+
+    public function getForeignKeyDeclaration(array $definition)
+    {
+        $sql  = $this->getForeignKeyBaseDeclaration($definition);
+        $sql .= $this->getAdvancedForeignKeyOptions($definition);
+        return $sql;
+    }
+
+    public function getAdvancedForeignKeyOptions(array $definition)
+    {
+        $query = '';
+        if ( ! empty($definition['onUpdate'])) {
+            $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialAction($definition['onUpdate']);
+        }
+        if ( ! empty($definition['onDelete'])) {
+            $query .= ' ON DELETE ' . $this->getForeignKeyReferentialAction($definition['onDelete']);
+        }
+        return $query;
+    }
+
+    public function getForeignKeyReferentialAction($action)
+    {
+        $upper = strtoupper($action);
+        switch ($upper) {
+            case 'CASCADE':
+            case 'SET NULL':
+            case 'NO ACTION':
+            case 'RESTRICT':
+            case 'SET DEFAULT':
+                return $upper;
+            break;
+            default:
+                throw new IPF_ORM_Exception('Unknown foreign key referential action \'' . $upper . '\' given.');
+        }
+    }
+
+    public function getForeignKeyBaseDeclaration(array $definition)
+    {
+        $sql = '';
+        if (isset($definition['name'])) {
+            $sql .= ' CONSTRAINT ' . $this->conn->quoteIdentifier($definition['name']) . ' ';
+        }
+        $sql .= 'FOREIGN KEY (';
+
+        if ( ! isset($definition['local'])) {
+            throw new IPF_ORM_Exception('Local reference field missing from definition.');
+        }
+        if ( ! isset($definition['foreign'])) {
+            throw new IPF_ORM_Exception('Foreign reference field missing from definition.');
+        }
+        if ( ! isset($definition['foreignTable'])) {
+            throw new IPF_ORM_Exception('Foreign reference table missing from definition.');
+        }
+
+        if ( ! is_array($definition['local'])) {
+            $definition['local'] = array($definition['local']);
+        }
+        if ( ! is_array($definition['foreign'])) {
+            $definition['foreign'] = array($definition['foreign']);
+        }
+
+        $sql .= implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['local']))
+              . ') REFERENCES '
+              . $this->conn->quoteIdentifier($definition['foreignTable']) . '('
+              . implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['foreign'])) . ')';
+
+        return $sql;
+    }
+
+    public function exportSortedClassesSql($classes, $groupByConnection = true)
+    {
+         $connections = array();
+         foreach ($classes as $class) {
+             $connection = IPF_ORM_Manager::connection();
+             $connectionName = $connection->getName();
+
+             if ( ! isset($connections[$connectionName])) {
+                 $connections[$connectionName] = array(
+                     'create_tables'    => array(),
+                     'create_indexes'   => array(),
+                     'alters'           => array()
+                 );
+             }
+
+             $sql = $this->exportClassesSql($class);
+
+             // Build array of all the creates
+             // We need these to happen first
+             foreach ($sql as $key => $query) {
+                 // If create table statement
+                 if (substr($query, 0, strlen('CREATE TABLE')) == 'CREATE TABLE') {
+                     $connections[$connectionName]['create_tables'][] = $query;
+
+                     unset($sql[$key]);
+                     continue;
+                 }
+
+                 // If create index statement
+                 if (preg_grep("/CREATE .* INDEX/", array($query))) {
+                     $connections[$connectionName]['create_indexes'][] =  $query;
+
+                     unset($sql[$key]);
+                     continue;
+                 }
+
+                 // If alter table statement
+                 if (substr($query, 0, strlen('ALTER TABLE')) == 'ALTER TABLE') {
+                     $connections[$connectionName]['alters'][] = $query;
+
+                     unset($sql[$key]);
+                     continue;
+                 }
+             }
+         }
+
+         // Loop over all the sql again to merge everything together so it is in the correct order
+         $build = array();
+         foreach ($connections as $connectionName => $sql) {
+             $build[$connectionName] = array_merge($sql['create_tables'], $sql['create_indexes'], $sql['alters']);
+         }
+
+         if ( ! $groupByConnection) {
+             $new = array();
+             foreach($build as $connectionname => $sql) {
+                 $new = array_merge($new, $sql);
+             }
+             $build = $new;
+         }
+         return $build;
+    }
+
+     public function exportClasses(array $classes)
+     {
+         $queries = $this->exportSortedClassesSql($classes);
+         
+         foreach ($queries as $connectionName => $sql) {
+             $connection = IPF_ORM_Manager::connection();
+
+             $connection->beginTransaction();
+
+             foreach ($sql as $query) {
+                 try {
+                     $connection->exec($query);
+                 } catch (IPF_ORM_Exception $e) {
+                     // we only want to silence table already exists errors
+                     if ($e->getPortableCode() !== IPF_ORM::ERR_ALREADY_EXISTS) {
+                         $connection->rollback();
+                         throw new IPF_ORM_Exception($e->getMessage() . '. Failing Query: ' . $query);
+                     }
+                 }
+             }
+             $connection->commit();
+         }
+     }
+
+    public function exportClassesSql($name)
+    {
+        $sql = array();
+
+        $table  = IPF_ORM::getTable($name);
+
+        $data = $this->getExportableFormat($table);
+
+        $query = $this->createTableSql($data['tableName'], $data['columns'], $data['options']);
+
+        if (is_array($query)) {
+            $sql = array_merge($sql, $query);
+        } else {
+            $sql[] = $query;
+        }
+
+        $sql = array_unique($sql);
+        
+        rsort($sql);
+
+        return $sql;
+    }
+
+    private function getExportableFormat($table)
+    {
+        $columns = array();
+        $primary = array();
+
+        foreach ($table->getColumns() as $name => $definition) {
+
+            if (isset($definition['owner'])) {
+                continue;
+            }
+
+            switch ($definition['type']) {
+                case 'enum':
+                    if (isset($definition['default'])) {
+                        $definition['default'] = $table->enumIndex($name, $definition['default']);
+                    }
+                    break;
+                case 'boolean':
+                    if (isset($definition['default'])) {
+                        $definition['default'] = $table->getConnection()->convertBooleans($definition['default']);
+                    }
+                    break;
+            }
+            $columns[$name] = $definition;
+
+            if (isset($definition['primary']) && $definition['primary']) {
+                $primary[] = $name;
+            }
+        }
+
+        $options['foreignKeys'] = $table->getOption('foreignKeys', array());
+
+        if ($table->getAttribute(IPF_ORM::ATTR_EXPORT) & IPF_ORM::EXPORT_CONSTRAINTS) {
+            $constraints = array();
+
+            $emptyIntegrity = array('onUpdate' => null,
+                                    'onDelete' => null);
+
+            foreach ($table->getRelations() as $name => $relation) {
+                $fk = $relation->toArray();
+                $fk['foreignTable'] = $relation->getTable()->getTableName();
+
+                if ($relation->getTable() === $table && in_array($relation->getLocal(), $primary)) {
+                    if ($relation->hasConstraint()) {
+                        throw new IPF_ORM_Exception("Badly constructed integrity constraints.");
+                    }
+                    continue;
+                }
+
+                $integrity = array('onUpdate' => $fk['onUpdate'],
+                                   'onDelete' => $fk['onDelete']);
+
+                if ($relation instanceof IPF_ORM_Relation_LocalKey) {
+                    $def = array('local'        => $relation->getLocal(),
+                                 'foreign'      => $relation->getForeign(),
+                                 'foreignTable' => $relation->getTable()->getTableName());
+
+                    if (($key = array_search($def, $options['foreignKeys'])) === false) {
+                        $options['foreignKeys'][] = $def;
+                        $constraints[] = $integrity;
+                    } else {
+                        if ($integrity !== $emptyIntegrity) {
+                            $constraints[$key] = $integrity;
+                        }
+                    }
+                }
+            }
+
+            foreach ($constraints as $k => $def) {
+                $options['foreignKeys'][$k] = array_merge($options['foreignKeys'][$k], $def);
+            }
+        }
+
+        $options['primary'] = $primary;
+        
+        return array('tableName' => $table->getOption('tableName'),
+                     'columns'   => $columns,
+                     'options'   => array_merge($table->getOptions(), $options));
+    }
+}
+
diff --git a/src/orm/export/mysql.php b/src/orm/export/mysql.php
new file mode 100644 (file)
index 0000000..1dbe90a
--- /dev/null
@@ -0,0 +1,493 @@
+<?php
+
+class IPF_ORM_Export_Mysql extends IPF_ORM_Export
+{
+    public function createTableSql($name, array $fields, array $options = array()) 
+    {
+        if ( ! $name)
+            throw new IPF_ORM_Exception('no valid table name specified');
+
+        if (empty($fields)) {
+            throw new IPF_ORM_Exception('no fields specified for table "'.$name.'"');
+        }
+        $queryFields = $this->getFieldDeclarationList($fields);
+
+        // build indexes for all foreign key fields (needed in MySQL!!)
+        if (isset($options['foreignKeys'])) {
+            foreach ($options['foreignKeys'] as $fk) {
+                $local = $fk['local'];
+                $found = false;
+                if (isset($options['indexes'])) {
+                    foreach ($options['indexes'] as $definition) {
+                        if (is_string($definition['fields'])) {
+                            // Check if index already exists on the column                            
+                            $found = ($local == $definition['fields']);                        
+                        } else if (in_array($local, $definition['fields']) && count($definition['fields']) === 1) {
+                            // Index already exists on the column
+                            $found = true;
+                        }
+                    }
+                }
+                if (isset($options['primary']) && !empty($options['primary']) &&
+                        in_array($local, $options['primary'])) {
+                    // field is part of the PK and therefore already indexed
+                    $found = true;
+                }
+                
+                if ( ! $found) {
+                    if (is_array($local)) {
+                      foreach($local as $localidx) {
+                        $options['indexes'][$localidx] = array('fields' => array($localidx => array()));
+                      }
+                    } else {
+                      $options['indexes'][$local] = array('fields' => array($local => array()));                      
+                    }
+                }
+            }
+        }
+
+        // add all indexes
+        if (isset($options['indexes']) && ! empty($options['indexes'])) {
+            foreach($options['indexes'] as $index => $definition) {
+                $queryFields .= ', ' . $this->getIndexDeclaration($index, $definition);
+            }
+        }
+
+        // attach all primary keys
+        if (isset($options['primary']) && ! empty($options['primary'])) {
+            $keyColumns = array_values($options['primary']);
+            $keyColumns = array_map(array($this->conn, 'quoteIdentifier'), $keyColumns);
+            $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
+        }
+
+        $query = 'CREATE TABLE ' . $this->conn->quoteIdentifier($name) . ' (' . $queryFields . ')';
+
+        $optionStrings = array();
+
+        if (isset($options['comment'])) {
+            $optionStrings['comment'] = 'COMMENT = ' . $this->conn->quote($options['comment'], 'text');
+        }
+        if (isset($options['charset'])) {
+            $optionStrings['charset'] = 'DEFAULT CHARACTER SET ' . $options['charset'];
+        }
+        if (isset($options['collate'])) {
+            $optionStrings['collate'] = 'COLLATE ' . $options['collate'];
+        }
+
+        $type = false;
+
+        // get the type of the table
+        if (isset($options['type'])) {
+            $type = $options['type'];
+        } else {
+            $type = $this->conn->getAttribute(IPF_ORM::ATTR_DEFAULT_TABLE_TYPE);
+        }
+
+        if ($type) {
+            $optionStrings[] = 'ENGINE = ' . $type;
+        }
+
+        if ( ! empty($optionStrings)) {
+            $query.= ' '.implode(' ', $optionStrings);
+        }
+        $sql[] = $query;
+
+        if (isset($options['foreignKeys'])) {
+
+            foreach ((array) $options['foreignKeys'] as $k => $definition) {
+                if (is_array($definition)) {
+                    $sql[] = $this->createForeignKeySql($name, $definition);
+                }
+            }
+        }
+        return $sql;
+    }
+
+    public function getDeclaration($name, array $field)
+    {
+        $declaration = $this->conn->quoteIdentifier($name) . ' ';
+
+        if (!isset($field['type']))
+            throw new IPF_ORM_Exception('Missing column type.');
+
+        switch ($field['type']) {
+            case 'char':
+                $length = ( ! empty($field['length'])) ? $field['length'] : false;
+
+                $declaration .= $length ? 'CHAR('.$length.')' : 'CHAR(255)';
+                break;
+            case 'varchar':
+            case 'array':
+            case 'object':
+            case 'string':
+            case 'gzip':
+                if (!isset($field['length'])) {
+                    if (array_key_exists('default', $field)) {
+                        $field['length'] = $this->conn->varchar_max_length;
+                    } else {
+                        $field['length'] = false;
+                    }
+                }
+
+                $length = ($field['length'] <= $this->conn->varchar_max_length) ? $field['length'] : false;
+                $fixed  = (isset($field['fixed'])) ? $field['fixed'] : false;
+
+                $declaration .= $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
+                    : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
+                break;
+            case 'clob':
+                if (!empty($field['length'])) {
+                    $length = $field['length'];
+                    if ($length <= 255) {
+                        return 'TINYTEXT';
+                    } elseif ($length <= 65532) {
+                        return 'TEXT';
+                    } elseif ($length <= 16777215) {
+                        return 'MEDIUMTEXT';
+                    }
+                }
+                $declaration .= 'LONGTEXT';
+                break;
+            case 'blob':
+                if ( ! empty($field['length'])) {
+                    $length = $field['length'];
+                    if ($length <= 255) {
+                        return 'TINYBLOB';
+                    } elseif ($length <= 65532) {
+                        return 'BLOB';
+                    } elseif ($length <= 16777215) {
+                        return 'MEDIUMBLOB';
+                    }
+                }
+                $declaration .= 'LONGBLOB';
+                break;
+            case 'enum':
+                if ($this->conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+                    $values = array();
+                    foreach ($field['values'] as $value) {
+                      $values[] = $this->conn->quote($value, 'varchar');
+                    }
+                    $declaration .= 'ENUM('.implode(', ', $values).')';
+                    break;
+                }
+                // fall back to integer
+            case 'integer':
+            case 'int':
+                $type = 'INT';
+                if (!empty($field['length'])) {
+                    $length = $field['length'];
+                    if ($length <= 1) {
+                        $type = 'TINYINT';
+                    } elseif ($length == 2) {
+                        $type = 'SMALLINT';
+                    } elseif ($length == 3) {
+                        $type = 'MEDIUMINT';
+                    } elseif ($length == 4) {
+                        $type = 'INT';
+                    } elseif ($length > 4) {
+                        $type = 'BIGINT';
+                    }
+                }
+                $declaration .= $type;
+                if (isset($field['unsigned']) && $field['unsigned'])
+                    $declaration .= ' UNSIGNED';
+                break;
+            case 'boolean':
+                $declaration .= 'TINYINT(1)';
+                break;
+            case 'date':
+                $declaration .= 'DATE';
+                break;
+            case 'time':
+                $declaration .= 'TIME';
+                break;
+            case 'datetime':
+                $declaration .= 'DATETIME';
+                break;
+            case 'timestamp':
+                $declaration .= 'TIMESTAMP';
+                break;
+            case 'float':
+            case 'double':
+                $declaration .= 'DOUBLE';
+                break;
+            case 'decimal':
+                $scale = !empty($field['scale']) ? $field['scale'] : $this->conn->getAttribute(IPF_ORM::ATTR_DECIMAL_PLACES);
+                if (!empty($field['length'])) {
+                    $length = $field['length'];
+                    if (is_array($length)) {
+                        list($length, $scale) = $length;
+                    }
+                } else {
+                    $length = 18;
+                }
+                $declaration .= 'DECIMAL('.$length.','.$scale.')';
+                break;
+            case 'bit':
+                $declaration .= 'BIT';
+                break;
+            default:
+                throw new IPF_ORM_Exception('Unknown field type \'' . $field['type'] .  '\'.');
+        }
+
+        if (isset($field['charset']) && $field['charset'])
+            $declaration .= ' CHARACTER SET ' . $field['charset'];
+
+        if (isset($field['collate']) && $field['collate'])
+            $declaration .= ' COLLATE ' . $field['collate'];
+
+        if (isset($field['notnull']) && $field['notnull'])
+            $declaration .= ' NOT NULL';
+
+        if (!empty($field['autoincrement'])) {
+            $declaration .= ' AUTO_INCREMENT';
+        } else {
+            $declaration .= $this->getDefaultFieldDeclaration($field);
+
+            if (isset($field['unique']) && $field['unique'])
+                $declaration .= ' UNIQUE';
+        }
+
+        if (isset($field['comment']) && $field['comment'])
+            $declaration .= ' COMMENT ' . $this->conn->quote($field['comment'], 'varchar');
+
+        return $declaration;
+    }
+
+    private function getDefaultFieldDeclaration($field)
+    {
+        $default = '';
+        if (isset($field['default']) && (!isset($field['length']) || $field['length'] <= 255)) {
+            if ($field['default'] === '') {
+                $field['default'] = empty($field['notnull']) ? null : $this->valid_default_values[$field['type']];
+            }
+
+            if ($field['default'] === '' && ($this->conn->getAttribute(IPF_ORM::ATTR_PORTABILITY) & IPF_ORM::PORTABILITY_EMPTY_TO_NULL))
+                $field['default'] = null;
+
+            if (is_null($field['default'])) {
+                $default = ' DEFAULT NULL';
+            } else {
+                if ($field['type'] === 'boolean') {
+                    $fieldType = 'boolean';
+                    $field['default'] = $this->conn->convertBooleans($field['default']);
+                } elseif ($field['type'] == 'enum' && $this->conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+                    $fieldType = 'varchar';
+                } else {
+                    $fieldType = $field['type'];
+                }
+                $default = ' DEFAULT ' . $this->conn->quote($field['default'], $fieldType);
+            }
+        }
+        return $default;
+    }
+
+    public function alterTableSql($name, array $changes, $check = false)
+    {
+        if ( ! $name) {
+            throw new IPF_ORM_Exception('no valid table name specified');
+        }
+        foreach ($changes as $changeName => $change) {
+            switch ($changeName) {
+                case 'add':
+                case 'remove':
+                case 'change':
+                case 'rename':
+                case 'name':
+                    break;
+                default:
+                    throw new IPF_ORM_Exception('change type "' . $changeName . '" not yet supported');
+            }
+        }
+
+        if ($check) {
+            return true;
+        }
+
+        $query = '';
+        if ( ! empty($changes['name'])) {
+            $change_name = $this->conn->quoteIdentifier($changes['name']);
+            $query .= 'RENAME TO ' . $change_name;
+        }
+
+        if ( ! empty($changes['add']) && is_array($changes['add'])) {
+            foreach ($changes['add'] as $fieldName => $field) {
+                if ($query) {
+                    $query.= ', ';
+                }
+                $query.= 'ADD ' . $this->getDeclaration($fieldName, $field);
+            }
+        }
+
+        if ( ! empty($changes['remove']) && is_array($changes['remove'])) {
+            foreach ($changes['remove'] as $fieldName => $field) {
+                if ($query) {
+                    $query .= ', ';
+                }
+                $fieldName = $this->conn->quoteIdentifier($fieldName);
+                $query .= 'DROP ' . $fieldName;
+            }
+        }
+
+        $rename = array();
+        if ( ! empty($changes['rename']) && is_array($changes['rename'])) {
+            foreach ($changes['rename'] as $fieldName => $field) {
+                $rename[$field['name']] = $fieldName;
+            }
+        }
+
+        if ( ! empty($changes['change']) && is_array($changes['change'])) {
+            foreach ($changes['change'] as $fieldName => $field) {
+                if ($query) {
+                    $query.= ', ';
+                }
+                if (isset($rename[$fieldName])) {
+                    $oldFieldName = $rename[$fieldName];
+                    unset($rename[$fieldName]);
+                } else {
+                    $oldFieldName = $fieldName;
+                }
+                $oldFieldName = $this->conn->quoteIdentifier($oldFieldName);
+                $query .= 'CHANGE ' . $oldFieldName . ' ' 
+                        . $this->getDeclaration($fieldName, $field['definition']);
+            }
+        }
+
+        if ( ! empty($rename) && is_array($rename)) {
+            foreach ($rename as $renameName => $renamedField) {
+                if ($query) {
+                    $query.= ', ';
+                }
+                $field = $changes['rename'][$renamedField];
+                $renamedField = $this->conn->quoteIdentifier($renamedField);
+                $query .= 'CHANGE ' . $renamedField . ' '
+                        . $this->getDeclaration($field['name'], $field['definition']);
+            }
+        }
+
+        if ( ! $query) {
+            return false;
+        }
+
+        $name = $this->conn->quoteIdentifier($name);
+        
+        return 'ALTER TABLE ' . $name . ' ' . $query;
+    }
+
+    public function createIndexSql($table, $name, array $definition)
+    {
+        $name   = $this->conn->quoteIdentifier($this->getIndexName($name));
+        $type   = '';
+        if (isset($definition['type'])) {
+            switch (strtolower($definition['type'])) {
+                case 'fulltext':
+                case 'unique':
+                    $type = strtoupper($definition['type']) . ' ';
+                break;
+                default:
+                    throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']);
+            }
+        }
+        $query  = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table;
+        $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
+
+        return $query;
+    }
+
+    public function getIndexDeclaration($name, array $definition)
+    {
+        $name   = $this->getIndexName($name);
+        $type   = '';
+        if (isset($definition['type'])) {
+            switch (strtolower($definition['type'])) {
+                case 'fulltext':
+                case 'unique':
+                    $type = strtoupper($definition['type']) . ' ';
+                break;
+                default:
+                    throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']);
+            }
+        }
+        
+        if ( ! isset($definition['fields'])) {
+            throw new IPF_ORM_Exception('No index columns given.');
+        }
+        if ( ! is_array($definition['fields'])) {
+            $definition['fields'] = array($definition['fields']);
+        }
+
+        $query = $type . 'INDEX ' . $this->conn->quoteIdentifier($name);
+
+        $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
+        
+        return $query;
+    }
+
+    public function getIndexFieldDeclarationList(array $fields)
+    {
+        $declFields = array();
+
+        foreach ($fields as $fieldName => $field) {
+            $fieldString = $this->conn->quoteIdentifier($fieldName);
+
+            if (is_array($field)) {
+                if (isset($field['length'])) {
+                    $fieldString .= '(' . $field['length'] . ')';
+                }
+
+                if (isset($field['sorting'])) {
+                    $sort = strtoupper($field['sorting']);
+                    switch ($sort) {
+                        case 'ASC':
+                        case 'DESC':
+                            $fieldString .= ' ' . $sort;
+                            break;
+                        default:
+                            throw new IPF_ORM_Exception('Unknown index sorting option given.');
+                    }
+                }
+            } else {
+                $fieldString = $this->conn->quoteIdentifier($field);
+            }
+            $declFields[] = $fieldString;
+        }
+        return implode(', ', $declFields);
+    }
+
+    public function getAdvancedForeignKeyOptions(array $definition)
+    {
+        $query = '';
+        if ( ! empty($definition['match'])) {
+            $query .= ' MATCH ' . $definition['match'];
+        }
+        if ( ! empty($definition['onUpdate'])) {
+            $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialAction($definition['onUpdate']);
+        }
+        if ( ! empty($definition['onDelete'])) {
+            $query .= ' ON DELETE ' . $this->getForeignKeyReferentialAction($definition['onDelete']);
+        }
+        return $query;
+    }
+
+    public function dropIndexSql($table, $name)
+    {
+        $table  = $this->conn->quoteIdentifier($table);
+        $name   = $this->conn->quoteIdentifier($this->getIndexName($name));
+        return 'DROP INDEX ' . $name . ' ON ' . $table;
+    }
+
+    public function dropTableSql($table)
+    {
+        $table  = $this->conn->quoteIdentifier($table);
+        return 'DROP TABLE ' . $table;
+    }
+
+    public function dropForeignKey($table, $name)
+    {
+        $table = $this->conn->quoteIdentifier($table);
+        $name  = $this->conn->quoteIdentifier($name);
+
+        return $this->conn->exec('ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $name);
+    }
+}
+
diff --git a/src/orm/expression.php b/src/orm/expression.php
new file mode 100644 (file)
index 0000000..a866412
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+class IPF_ORM_Expression
+{
+    protected $_expression;
+    protected $_conn;
+    protected $_tokenizer;
+
+    public function __construct($expr, $conn = null)
+    {
+        $this->_tokenizer = new IPF_ORM_Query_Tokenizer();
+        $this->setExpression($expr);
+        if ($conn !== null) {
+            $this->_conn = $conn;
+        }
+    }
+
+    public function getConnection()
+    {
+        if ( ! isset($this->_conn)) {
+            return IPF_ORM_Manager::connection();
+        }
+
+        return $this->_conn;
+    }
+
+    public function setExpression($clause)
+    {
+        $this->_expression = $this->parseClause($clause);
+    }
+
+    public function parseExpression($expr)
+    {
+        $pos  = strpos($expr, '(');
+        $quoted = (substr($expr, 0, 1) === "'" && substr($expr, -1) === "'");
+        if ($pos === false || $quoted) {
+            return $expr;
+        }
+
+        // get the name of the function
+        $name   = substr($expr, 0, $pos);
+        $argStr = substr($expr, ($pos + 1), -1);
+
+        // parse args
+        foreach ($this->_tokenizer->bracketExplode($argStr, ',') as $arg) {
+           $args[] = $this->parseClause($arg);
+        }
+
+        return call_user_func_array(array($this->getConnection()->expression, $name), $args);
+    }
+
+    public function parseClause($clause)
+    {
+        $e = $this->_tokenizer->bracketExplode($clause, ' ');
+
+        foreach ($e as $k => $expr) {
+            $e[$k] = $this->parseExpression($expr);
+        }
+        
+        return implode(' ', $e);
+    }
+
+    public function getSql()
+    {
+        return $this->_expression;
+    }
+
+    public function __toString()
+    {
+        return $this->getSql();
+    }
+}
diff --git a/src/orm/expression/driver.php b/src/orm/expression/driver.php
new file mode 100644 (file)
index 0000000..5c7baa2
--- /dev/null
@@ -0,0 +1,288 @@
+<?php
+
+class IPF_ORM_Expression_Driver extends IPF_ORM_Connection_Module
+{
+    public function getIdentifier($column)
+    {
+        return $column;
+    }
+    public function getIdentifiers($columns)
+    {
+        return $columns;
+    }
+
+    public function regexp()
+    {
+        throw new IPF_ORM_Exception('Regular expression operator is not supported by this database driver.');
+    }
+
+    public function avg($column)
+    {
+        $column = $this->getIdentifier($column);
+        return 'AVG(' .  $column . ')';
+    }
+
+    public function count($column)
+    {
+        $column = $this->getIdentifier($column);
+        return 'COUNT(' . $column . ')';
+    }
+
+    public function max($column)
+    {
+        $column = $this->getIdentifier($column);
+        return 'MAX(' . $column . ')';
+    }
+
+    public function min($column)
+    {
+        $column = $this->getIdentifier($column);
+        return 'MIN(' . $column . ')';
+    }
+
+    public function sum($column)
+    {
+        $column = $this->getIdentifier($column);
+        return 'SUM(' . $column . ')';
+    }
+
+    public function md5($column)
+    {
+        $column = $this->getIdentifier($column);
+        return 'MD5(' . $column . ')';
+    }
+
+    public function length($column)
+    {
+        $column = $this->getIdentifier($column);
+        return 'LENGTH(' . $column . ')';
+    }
+
+    public function round($column, $decimals = 0)
+    {
+        $column = $this->getIdentifier($column);
+
+        return 'ROUND(' . $column . ', ' . $decimals . ')';
+    }
+
+    public function mod($expression1, $expression2)
+    {
+        $expression1 = $this->getIdentifier($expression1);
+        $expression2 = $this->getIdentifier($expression2);
+        return 'MOD(' . $expression1 . ', ' . $expression2 . ')';
+    }
+
+    public function trim($str)
+    {
+        return 'TRIM(' . $str . ')';
+    }
+
+    public function rtrim($str)
+    {
+        return 'RTRIM(' . $str . ')';
+    }
+
+    public function ltrim($str)
+    {
+        return 'LTRIM(' . $str . ')';
+    }
+
+    public function upper($str)
+    {
+        return 'UPPER(' . $str . ')';
+    }
+
+    public function lower($str)
+    {
+        return 'LOWER(' . $str . ')';
+    }
+
+    public function locate($str, $substr)
+    {
+        return 'LOCATE(' . $str . ', ' . $substr . ')';
+    }
+
+    public function now()
+    {
+        return 'NOW()';
+    }
+
+    public function coalesce($expression1, $expression2)
+    {
+        $expression1 = $this->getIdentifier($expression1);
+        $expression2 = $this->getIdentifier($expression2);
+        return 'COALESCE(' . $expression1 . ', ' . $expression2 . ')';
+    }
+
+    public function soundex($value)
+    {
+        throw new IPF_ORM_Exception('SQL soundex function not supported by this driver.');
+    }
+
+    public function substring($value, $from, $len = null)
+    {
+        $value = $this->getIdentifier($value);
+        if ($len === null)
+            return 'SUBSTRING(' . $value . ' FROM ' . $from . ')';
+        else {
+            $len = $this->getIdentifier($len);
+            return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $len . ')';
+        }
+    }
+
+    public function concat()
+    {
+        $args = func_get_args();
+
+        return 'CONCAT(' . join(', ', (array) $args) . ')';
+    }
+
+    public function not($expression)
+    {
+        $expression = $this->getIdentifier($expression);
+        return 'NOT(' . $expression . ')';
+    }
+
+    private function basicMath($type, array $args)
+    {
+        $elements = $this->getIdentifiers($args);
+        if (count($elements) < 1) {
+            return '';
+        }
+        if (count($elements) == 1) {
+            return $elements[0];
+        } else {
+            return '(' . implode(' ' . $type . ' ', $elements) . ')';
+        }
+    }
+
+    public function add(array $args)
+    {
+        return $this->basicMath('+', $args);
+    }
+
+    public function sub(array $args)
+    {
+        return $this->basicMath('-', $args );
+    }
+
+    public function mul(array $args)
+    {
+        return $this->basicMath('*', $args);
+    }
+
+    public function div(array $args)
+    {
+        return $this->basicMath('/', $args);
+    }
+
+    public function eq($value1, $value2)
+    {
+        $value1 = $this->getIdentifier($value1);
+        $value2 = $this->getIdentifier($value2);
+        return $value1 . ' = ' . $value2;
+    }
+
+    public function neq($value1, $value2)
+    {
+        $value1 = $this->getIdentifier($value1);
+        $value2 = $this->getIdentifier($value2);
+        return $value1 . ' <> ' . $value2;
+    }
+
+    public function gt($value1, $value2)
+    {
+        $value1 = $this->getIdentifier($value1);
+        $value2 = $this->getIdentifier($value2);
+        return $value1 . ' > ' . $value2;
+    }
+
+    public function gte($value1, $value2)
+    {
+        $value1 = $this->getIdentifier($value1);
+        $value2 = $this->getIdentifier($value2);
+        return $value1 . ' >= ' . $value2;
+    }
+
+    public function lt($value1, $value2)
+    {
+        $value1 = $this->getIdentifier($value1);
+        $value2 = $this->getIdentifier($value2);
+        return $value1 . ' < ' . $value2;
+    }
+
+    public function lte($value1, $value2)
+    {
+        $value1 = $this->getIdentifier($value1);
+        $value2 = $this->getIdentifier($value2);
+        return $value1 . ' <= ' . $value2;
+    }
+
+    public function in($column, $values)
+    {
+        if ( ! is_array($values)) {
+            $values = array($values);
+        }
+        $values = $this->getIdentifiers($values);
+        $column = $this->getIdentifier($column);
+
+        if (count($values) == 0) {
+            throw new IPF_ORM_Exception('Values array for IN operator should not be empty.');
+        }
+        return $column . ' IN (' . implode(', ', $values) . ')';
+    }
+
+    public function isNull($expression)
+    {
+        $expression = $this->getIdentifier($expression);
+        return $expression . ' IS NULL';
+    }
+
+    public function isNotNull($expression)
+    {
+        $expression = $this->getIdentifier($expression);
+        return $expression . ' IS NOT NULL';
+    }
+
+    public function between($expression, $value1, $value2)
+    {
+        $expression = $this->getIdentifier($expression);
+        $value1 = $this->getIdentifier($value1);
+        $value2 = $this->getIdentifier($value2);
+        return $expression . ' BETWEEN ' .$value1 . ' AND ' . $value2;
+    }
+
+    public function guid()
+    {
+        throw new IPF_ORM_Exception('method not implemented');
+    }
+
+    public function acos($value)
+    {
+        return 'ACOS(' . $value . ')';
+    }
+
+    public function sin($value)
+    {
+        return 'SIN(' . $value . ')';
+    }
+
+    public function pi()
+    {
+        return 'PI()';
+    }
+
+    public function cos($value)
+    {
+        return 'COS(' . $value . ')';
+    }
+
+    public function __call($m, $a) 
+    {
+        if ($this->conn->getAttribute(IPF_ORM::ATTR_PORTABILITY) & IPF_ORM::PORTABILITY_EXPR) {
+            throw new IPF_ORM_Exception('Unknown expression ' . $m);
+        }
+        return $m . '(' . implode(', ', $a) . ')';
+    }
+}
+
diff --git a/src/orm/expression/mysql.php b/src/orm/expression/mysql.php
new file mode 100644 (file)
index 0000000..cc11fc6
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+class IPF_ORM_Expression_Mysql extends IPF_ORM_Expression_Driver
+{
+    public function regexp()
+    {
+        return 'RLIKE';
+    }
+
+    public function random()
+    {
+        return 'RAND()';
+    }
+
+    public function guid()
+    {
+        return 'UUID()';
+    }
+
+    public function year($column)
+    {
+        $column = $this->getIdentifier($column);
+        return 'YEAR(' .  $column . ')';
+    }
+
+    public function month($column)
+    {
+        $column = $this->getIdentifier($column);
+        return 'MONTH(' .  $column . ')';
+    }
+
+    public function monthname($column)
+    {
+        $column = $this->getIdentifier($column);
+        return 'MONTHNAME(' .  $column . ')';
+    }
+
+    public function day($column)
+    {
+        $column = $this->getIdentifier($column);
+        return 'DAY(' .  $column . ')';
+    }
+}
diff --git a/src/orm/hydrator.php b/src/orm/hydrator.php
new file mode 100644 (file)
index 0000000..a4bb6bf
--- /dev/null
@@ -0,0 +1,271 @@
+<?php
+
+class IPF_ORM_Hydrator extends IPF_ORM_Hydrator_Abstract
+{
+    public function hydrateResultSet($stmt, $tableAliases)
+    {
+        $hydrationMode = $this->_hydrationMode;
+
+        $this->_tableAliases = $tableAliases;
+
+        if ($hydrationMode == IPF_ORM::HYDRATE_NONE) {
+            return $stmt->fetchAll(PDO::FETCH_NUM);
+        }
+
+        if ($hydrationMode == IPF_ORM::HYDRATE_ARRAY) {
+            $driver = new IPF_ORM_Hydrator_ArrayDriver();
+        } else {
+            $driver = new IPF_ORM_Hydrator_RecordDriver();
+        }
+
+        // Used variables during hydration
+        reset($this->_queryComponents);
+        $rootAlias = key($this->_queryComponents);
+        $rootComponentName = $this->_queryComponents[$rootAlias]['table']->getComponentName();
+        // if only one component is involved we can make our lives easier
+        $isSimpleQuery = count($this->_queryComponents) <= 1;
+        // Holds the resulting hydrated data structure
+        $result = array();
+        // Lookup map to quickly discover/lookup existing records in the result
+        $identifierMap = array();
+        // Holds for each component the last previously seen element in the result set
+        $prev = array();
+        // holds the values of the identifier/primary key fields of components,
+        // separated by a pipe '|' and grouped by component alias (r, u, i, ... whatever)
+        // the $idTemplate is a prepared template. $id is set to a fresh template when
+        // starting to process a row.
+        $id = array();
+        $idTemplate = array();
+
+        $result = $driver->getElementCollection($rootComponentName);
+
+        if ($stmt === false || $stmt === 0) {
+            return $result;
+        }
+
+        // Initialize
+        foreach ($this->_queryComponents as $dqlAlias => $data) {
+            $componentName = $data['table']->getComponentName();
+            $identifierMap[$dqlAlias] = array();
+            $prev[$dqlAlias] = null;
+            $idTemplate[$dqlAlias] = '';
+        }
+
+        $event = new IPF_ORM_Event(null, IPF_ORM_Event::HYDRATE, null);
+
+        // Process result set
+        $cache = array();
+        while ($data = $stmt->fetch(IPF_ORM::FETCH_ASSOC)) {
+            $id = $idTemplate; // initialize the id-memory
+            $nonemptyComponents = array();
+            $rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents);
+
+            //
+            // hydrate the data of the root component from the current row
+            //
+            $table = $this->_queryComponents[$rootAlias]['table'];
+            $componentName = $table->getComponentName();
+            // Ticket #1115 (getInvoker() should return the component that has addEventListener)
+            $event->setInvoker($table);
+            $event->set('data', $rowData[$rootAlias]);
+            $table->notifyRecordListeners('preHydrate', $event);
+
+            $index = false;
+
+            // Check for an existing element
+            if ($isSimpleQuery || ! isset($identifierMap[$rootAlias][$id[$rootAlias]])) {
+                $element = $driver->getElement($rowData[$rootAlias], $componentName);
+                $event->set('data', $element);
+                $table->notifyRecordListeners('postHydrate', $event);
+
+                // do we need to index by a custom field?
+                if ($field = $this->_getCustomIndexField($rootAlias)) {
+                    if (isset($result[$field])) {
+                        throw new IPF_ORM_Exception("Couldn't hydrate. Found non-unique key mapping.");
+                    } else if ( ! isset($element[$field])) {
+                        throw new IPF_ORM_Exception("Couldn't hydrate. Found a non-existent key.");
+                    }
+                    $result[$element[$field]] = $element;
+                } else {
+                    $result[] = $element;
+                }
+
+                $identifierMap[$rootAlias][$id[$rootAlias]] = $driver->getLastKey($result);
+            } else {
+                $index = $identifierMap[$rootAlias][$id[$rootAlias]];
+            }
+
+            $this->_setLastElement($prev, $result, $index, $rootAlias, false);
+            unset($rowData[$rootAlias]);
+
+            // end hydrate data of the root component for the current row
+
+
+            // $prev[$rootAlias] now points to the last element in $result.
+            // now hydrate the rest of the data found in the current row, that belongs to other
+            // (related) components.
+            foreach ($rowData as $dqlAlias => $data) {
+                $index = false;
+                $map = $this->_queryComponents[$dqlAlias];
+                $table = $map['table'];
+                $componentName = $table->getComponentName();
+                $event->set('data', $data);
+                $table->notifyRecordListeners('preHydrate', $event);
+
+                $parent = $map['parent'];
+                $relation = $map['relation'];
+                $relationAlias = $map['relation']->getAlias();
+
+                $path = $parent . '.' . $dqlAlias;
+
+                if ( ! isset($prev[$parent])) {
+                    unset($prev[$dqlAlias]); // Ticket #1228
+                    continue;
+                }
+
+                // check the type of the relation
+                if ( ! $relation->isOneToOne() && $driver->initRelated($prev[$parent], $relationAlias)) {
+                    $oneToOne = false;
+                    // append element
+                    if (isset($nonemptyComponents[$dqlAlias])) {
+                        $indexExists = isset($identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
+                        $index = $indexExists ? $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
+                        $indexIsValid = $index !== false ? isset($prev[$parent][$relationAlias][$index]) : false;
+                        if ( ! $indexExists || ! $indexIsValid) {
+                            $element = $driver->getElement($data, $componentName);
+                            $event->set('data', $element);
+                            $table->notifyRecordListeners('postHydrate', $event);
+
+                            if ($field = $this->_getCustomIndexField($dqlAlias)) {
+                                if (isset($prev[$parent][$relationAlias][$element[$field]])) {
+                                    throw new IPF_ORM_Exception("Couldn't hydrate. Found non-unique key mapping.");
+                                } else if ( ! isset($element[$field])) {
+                                    throw new IPF_ORM_Exception("Couldn't hydrate. Found a non-existent key.");
+                                }
+                                $prev[$parent][$relationAlias][$element[$field]] = $element;
+                            } else {
+                                $prev[$parent][$relationAlias][] = $element; 
+                            }
+                            $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $driver->getLastKey($prev[$parent][$relationAlias]);                            
+                        }
+                        // register collection for later snapshots
+                        $driver->registerCollection($prev[$parent][$relationAlias]);
+                    }
+                } else {
+                    // 1-1 relation
+                    $oneToOne = true;
+
+                    if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($prev[$parent][$relationAlias])) {
+                        $prev[$parent][$relationAlias] = $driver->getNullPointer();
+                    } else if ( ! isset($prev[$parent][$relationAlias])) {
+                        $element = $driver->getElement($data, $componentName);
+
+                                               // [FIX] Tickets #1205 and #1237
+                        $event->set('data', $element);
+                        $table->notifyRecordListeners('postHydrate', $event);
+
+                        $prev[$parent][$relationAlias] = $element;
+                    }
+                }
+                
+                $coll =& $prev[$parent][$relationAlias];
+                $this->_setLastElement($prev, $coll, $index, $dqlAlias, $oneToOne);
+            }
+        }
+
+        $stmt->closeCursor();
+        $driver->flush();
+        //$e = microtime(true);
+        //echo 'Hydration took: ' . ($e - $s) . ' for '.count($result).' records<br />';
+
+        return $result;
+    }
+
+    protected function _setLastElement(&$prev, &$coll, $index, $dqlAlias, $oneToOne)
+    {
+        if (IPF_ORM_Null::isNull($coll) || $coll === null) {
+            unset($prev[$dqlAlias]); // Ticket #1228
+            return;
+        }
+
+        if ($index !== false) {
+            // Link element at $index to previous element for the component
+            // identified by the DQL alias $alias
+            $prev[$dqlAlias] =& $coll[$index];
+            return;
+        }
+
+        if (is_array($coll) && $coll) {
+            if ($oneToOne) {
+                $prev[$dqlAlias] =& $coll;
+            } else {
+                end($coll);
+                $prev[$dqlAlias] =& $coll[key($coll)];
+            }
+        } else if (count($coll) > 0) {
+            $prev[$dqlAlias] = $coll->getLast();
+        }
+    }
+
+    protected function _gatherRowData(&$data, &$cache, &$id, &$nonemptyComponents)
+    {
+        $rowData = array();
+
+        foreach ($data as $key => $value) {
+            // Parse each column name only once. Cache the results. 
+            if ( ! isset($cache[$key])) {
+                // check ignored names. fastest solution for now. if we get more we'll start
+                // to introduce a list.
+                if ($key == 'IPF_ORM_ROWNUM') continue;
+                $e = explode('__', $key);
+                $last = strtolower(array_pop($e));
+                $cache[$key]['dqlAlias'] = $this->_tableAliases[strtolower(implode('__', $e))];
+                $table = $this->_queryComponents[$cache[$key]['dqlAlias']]['table'];
+                $fieldName = $table->getFieldName($last);
+                $cache[$key]['fieldName'] = $fieldName;
+                if ($table->isIdentifier($fieldName)) {
+                    $cache[$key]['isIdentifier'] = true;
+                } else {
+                  $cache[$key]['isIdentifier'] = false;
+                }
+                $type = $table->getTypeOfColumn($last);
+                if ($type == 'integer' || $type == 'string') {
+                    $cache[$key]['isSimpleType'] = true;
+                } else {
+                    $cache[$key]['type'] = $type;
+                    $cache[$key]['isSimpleType'] = false;
+                }
+            }
+
+            $map = $this->_queryComponents[$cache[$key]['dqlAlias']];
+            $table = $map['table'];
+            $dqlAlias = $cache[$key]['dqlAlias'];
+            $fieldName = $cache[$key]['fieldName'];
+            if (isset($this->_queryComponents[$dqlAlias]['agg'][$fieldName])) {
+                $fieldName = $this->_queryComponents[$dqlAlias]['agg'][$fieldName];
+            }
+
+            if ($cache[$key]['isIdentifier']) {
+                $id[$dqlAlias] .= '|' . $value;
+            }
+
+            if ($cache[$key]['isSimpleType']) {
+                $rowData[$dqlAlias][$fieldName] = $value;
+            } else {
+                $rowData[$dqlAlias][$fieldName] = $table->prepareValue(
+                        $fieldName, $value, $cache[$key]['type']);
+            }
+
+            if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) {
+                $nonemptyComponents[$dqlAlias] = true;
+            }
+        }
+
+        return $rowData;
+    }
+
+    protected function _getCustomIndexField($alias)
+    {
+        return isset($this->_queryComponents[$alias]['map']) ? $this->_queryComponents[$alias]['map'] : null;
+    }
+}
diff --git a/src/orm/hydrator/abstract.php b/src/orm/hydrator/abstract.php
new file mode 100644 (file)
index 0000000..497d2ca
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+abstract class IPF_ORM_Hydrator_Abstract
+{
+    protected $_queryComponents = array();
+
+    protected $_hydrationMode = IPF_ORM::HYDRATE_RECORD;
+
+    public function __construct() {}
+
+    public function setHydrationMode($hydrationMode)
+    {
+        $this->_hydrationMode = $hydrationMode;
+    }
+
+    public function getHydrationMode()
+    {
+        return $this->_hydrationMode;
+    }
+
+    public function setQueryComponents(array $queryComponents)
+    {
+        $this->_queryComponents = $queryComponents;
+    }
+
+    public function getQueryComponents()
+    {
+        return $this->_queryComponents;
+    }
+
+    abstract public function hydrateResultSet($stmt, $tableAliases);
+}
diff --git a/src/orm/hydrator/arraydriver.php b/src/orm/hydrator/arraydriver.php
new file mode 100644 (file)
index 0000000..6018d62
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+class IPF_ORM_Hydrator_ArrayDriver
+{
+    public function getElementCollection($component)
+    {
+        return array();
+    }
+    public function getElement(array $data, $component)
+    {
+        return $data;
+    }
+
+    public function registerCollection($coll)
+    {
+    }
+
+    public function initRelated(array &$data, $name)
+    {
+        if ( ! isset($data[$name])) {
+            $data[$name] = array();
+        }
+        return true;
+    }
+
+    public function getNullPointer() 
+    {
+        return null;    
+    }
+
+    public function getLastKey(&$data)
+    {
+        end($data);
+        return key($data);
+    }
+
+    public function flush()
+    {
+    }
+}
diff --git a/src/orm/hydrator/recorddriver.php b/src/orm/hydrator/recorddriver.php
new file mode 100644 (file)
index 0000000..df7f498
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+
+class IPF_ORM_Hydrator_RecordDriver
+{
+    protected $_collections = array();
+    protected $_tables = array();
+    private $_initializedRelations = array();
+
+    public function getElementCollection($component)
+    {
+        $coll = new IPF_ORM_Collection($component);
+        $this->_collections[] = $coll;
+
+        return $coll;
+    }
+
+    public function getLastKey($coll) 
+    {
+        $coll->end();
+        
+        return $coll->key();
+    }
+    
+    public function initRelated(IPF_ORM_Record $record, $name)
+    {
+        if ( ! isset($this->_initializedRelations[$record->getOid()][$name])) {
+            $relation = $record->getTable()->getRelation($name);
+            $coll = new IPF_ORM_Collection($relation->getTable()->getComponentName());
+            $coll->setReference($record, $relation);
+            $record[$name] = $coll;
+            $this->_initializedRelations[$record->getOid()][$name] = true;
+        }
+        return true;
+    }
+    
+    public function registerCollection(IPF_ORM_Collection $coll)
+    {
+        $this->_collections[] = $coll;
+    }
+    
+    public function getNullPointer()
+    {
+        return IPF_ORM_Null::getInstance();
+    }
+    
+    public function getElement(array $data, $component)
+    {
+        $component = $this->_getClassNameToReturn($data, $component);
+        if ( ! isset($this->_tables[$component])) {
+            $this->_tables[$component] = IPF_ORM::getTable($component);
+            $this->_tables[$component]->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, false);
+        }
+
+        $this->_tables[$component]->setData($data);
+        $record = $this->_tables[$component]->getRecord();
+
+        return $record;
+    }
+    
+    public function flush()
+    {
+        // take snapshots from all initialized collections
+        foreach ($this->_collections as $key => $coll) {
+            $coll->takeSnapshot();
+        }
+        foreach ($this->_tables as $table) {
+            $table->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, true);
+        }
+        $this->_initializedRelations = null;
+        $this->_collections = null;
+        $this->_tables = null;
+    }
+    
+    protected function _getClassNameToReturn(array &$data, $component)
+    {
+        if ( ! isset($this->_tables[$component])) {
+            $this->_tables[$component] = IPF_ORM::getTable($component);
+            $this->_tables[$component]->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, false);
+        }
+        
+        if ( ! ($subclasses = $this->_tables[$component]->getOption('subclasses'))) {
+            return $component;
+        }
+        
+        foreach ($subclasses as $subclass) {
+            $table = IPF_ORM::getTable($subclass);
+            $inheritanceMap = $table->getOption('inheritanceMap');
+            list($key, $value) = each($inheritanceMap);
+            if ( ! isset($data[$key]) || $data[$key] != $value) {
+                continue;
+            } else {
+                return $table->getComponentName();
+            }
+        }
+        return $component;
+    }
+}
diff --git a/src/orm/import/builder.php b/src/orm/import/builder.php
new file mode 100644 (file)
index 0000000..9aaae70
--- /dev/null
@@ -0,0 +1,388 @@
+<?php
+
+class IPF_ORM_Import_Builder
+{
+    private $_baseClassPrefix = 'Base';
+    private $_baseClassesDirectory = IPF_ORM::BASE_CLASSES_DIRECTORY;
+
+    private static function varExport($var)
+    {
+        $export = var_export($var, true);
+        $export = preg_replace('#\s*\(\s*#', '(', $export);
+        $export = preg_replace('#[\s,]+\)#', ')', $export);
+        $export = preg_replace('#\s+#', ' ', $export);
+        return $export;
+    }
+
+    private static function varExportList($list)
+    {
+        $export = array();
+        foreach ($list as $var) {
+            $export[] = self::varExport($var);
+        }
+        return 'array('.implode(', ', $export).')';
+    }
+
+    private function buildTableDefinition(array $definition)
+    {
+        if (isset($definition['inheritance']['type']) && ($definition['inheritance']['type'] == 'simple' || $definition['inheritance']['type'] == 'column_aggregation')) {
+            return;
+        }
+
+        $ret = array(
+            '  public static function setTableDefinition(IPF_ORM_Table $table)',
+            '  {',
+        );
+
+        if (isset($definition['inheritance']['type']) && $definition['inheritance']['type'] == 'concrete')
+            $ret[] = '    parent::setTableDefinition($table);';
+
+        if (isset($definition['tableName']) && !empty($definition['tableName']))
+            $ret[] = "    ".'$table->setTableName(\''. $definition['tableName'].'\');';
+
+        if (isset($definition['columns']) && is_array($definition['columns']) && !empty($definition['columns']))
+            $ret[] = $this->buildColumns($definition['columns']);
+
+        if (isset($definition['ordering']) && is_array($definition['ordering']) && !empty($definition['ordering']))
+            $ret[] = '    $table->setOrdering('.self::varExportList($definition['ordering']).');';
+
+        if (isset($definition['indexes']) && is_array($definition['indexes']) && !empty($definition['indexes']))
+            foreach ($definition['indexes'] as $indexName => $definitions)
+                $ret[] = "    \$table->addIndex('" . $indexName . "', " . self::varExport($definitions) . ');';
+
+        if (isset($definition['attributes']) && is_array($definition['attributes']) && !empty($definition['attributes']))
+            $ret[] = $this->buildAttributes($definition['attributes']);
+
+        if (isset($definition['options']) && is_array($definition['options']) && !empty($definition['options']))
+            $ret[] = $this->buildOptions($definition['options']);
+
+        if (isset($definition['inheritance']['subclasses']) && !empty($definition['inheritance']['subclasses'])) {
+            $ret[] = "    ".'$table->setSubClasses('. self::varExport($definition['inheritance']['subclasses']).');';
+        }
+
+        $ret[] = '  }';
+
+        return implode(PHP_EOL, $ret);
+    }
+
+    private function buildSetUp(array $definition)
+    {
+        $ret = array();
+
+        if (isset($definition['relations']) && is_array($definition['relations']) && !empty($definition['relations'])) {
+            foreach ($definition['relations'] as $name => $relation) {
+                $class = isset($relation['class']) ? $relation['class'] : $name;
+                $alias = (isset($relation['alias']) && $relation['alias'] !== $relation['class']) ? $relation['alias'] : '';
+
+                if (!isset($relation['type']))
+                    $relation['type'] = IPF_ORM_Relation::ONE;
+
+                if ($relation['type'] === IPF_ORM_Relation::ONE ||
+                    $relation['type'] === IPF_ORM_Relation::ONE_COMPOSITE) {
+                    $r = "    \$table->hasOne('$class', '$alias'";
+                } else {
+                    $r = "    \$table->hasMany('$class', '$alias'";
+                }
+
+                $a = array();
+
+                if (isset($relation['refClass'])) {
+                    $a[] = '\'refClass\' => ' . self::varExport($relation['refClass']);
+                }
+
+                if (isset($relation['deferred']) && $relation['deferred']) {
+                    $a[] = '\'default\' => ' . self::varExport($relation['deferred']);
+                }
+
+                if (isset($relation['local']) && $relation['local']) {
+                    $a[] = '\'local\' => ' . self::varExport($relation['local']);
+                }
+
+                if (isset($relation['foreign']) && $relation['foreign']) {
+                    $a[] = '\'foreign\' => ' . self::varExport($relation['foreign']);
+                }
+
+                if (isset($relation['onDelete']) && $relation['onDelete']) {
+                    $a[] = '\'onDelete\' => ' . self::varExport($relation['onDelete']);
+                }
+
+                if (isset($relation['onUpdate']) && $relation['onUpdate']) {
+                    $a[] = '\'onUpdate\' => ' . self::varExport($relation['onUpdate']);
+                }
+
+                if (isset($relation['equal']) && $relation['equal']) {
+                    $a[] = '\'equal\' => ' . self::varExport($relation['equal']);
+                }
+
+                if (isset($relation['owningSide']) && $relation['owningSide']) {
+                    $a[] = '\'owningSide\' => ' . self::varExport($relation['owningSide']);
+                }
+
+                if (isset($relation['exclude']) && $relation['exclude']) {
+                    $a[] = '\'exclude\' => ' . self::varExport($relation['exclude']);
+                }
+
+                if (!empty($a))
+                    $r .= ', array(' . implode(', ', $a) . ')';
+
+                $ret[] = $r.');';
+            }
+        }
+
+        if (isset($definition['templates']) && is_array($definition['templates']) && !empty($definition['templates'])) {
+            $this->buildTemplates($definition['templates'], $ret);
+        }
+
+        if (isset($definition['actAs']) && is_array($definition['actAs']) && !empty($definition['actAs'])) {
+            $this->buildActAs($definition['actAs'], $ret);
+        }
+
+        if (isset($definition['listeners']) && is_array($definition['listeners']) && !empty($definition['listeners'])) {
+            foreach ($definition['listeners'] as $listener) {
+                $ret[] = "    \$table->listeners['$listener'] = new $listener;";
+            }
+        }
+
+        // If the body of the function has contents and we are using inheritance
+        // then we need call the parent::setUp() before the body of the function
+        // Class table inheritance is the only one we shouldn't call parent::setUp() for
+        if (count($ret) && isset($definition['inheritance']['type']) && $definition['inheritance']['type'] != 'class_table') {
+            array_unshift($ret, '    parent::setUp($table);');
+        }
+
+        // If we have some code for the function then lets define it and return it
+        if (count($ret)) {
+            array_unshift($ret,
+                '  public static function setUp(IPF_ORM_Table $table)',
+                '  {'
+            );
+            $ret[] = '  }';
+        }
+        return $ret;
+    }
+
+    private function buildColumns(array $columns)
+    {
+        $result = array();
+        foreach ($columns as $name => $column) {
+            $columnName = isset($column['name']) ? $column['name']:$name;
+            $build = "    ".'$table->setColumn(\'' . $columnName . '\', \'' . $column['type'] . '\'';
+
+            if ($column['length']) {
+                if (is_numeric($column['length']))
+                    $build .= ', ' . $column['length'];
+                else
+                    $build .= ', array(' . $column['length'] . ')';
+            } else {
+                $build .= ', null';
+            }
+
+            $options = $column;
+
+            // Remove name, alltypes, ntype. They are not needed in options array
+            unset($options['name']);
+            unset($options['alltypes']);
+            unset($options['ntype']);
+
+            // Remove notnull => true if the column is primary
+            // Primary columns are implied to be notnull in IPF_ORM
+            if (isset($options['primary']) && $options['primary'] == true && (isset($options['notnull']) && $options['notnull'] == true)) {
+                unset($options['notnull']);
+            }
+
+            // Remove default if the value is 0 and the column is a primary key
+            // IPF_ORM defaults to 0 if it is a primary key
+            if (isset($options['primary']) && $options['primary'] == true && (isset($options['default']) && $options['default'] == 0)) {
+                unset($options['default']);
+            }
+
+            // These can be removed if they are empty. They all default to a false/0/null value anyways
+            $remove = array('fixed', 'primary', 'notnull', 'autoincrement', 'unsigned');
+            foreach ($remove as $key) {
+                if (isset($options[$key]) && empty($options[$key])) {
+                    unset($options[$key]);
+                }
+            }
+
+            // Remove null and empty array values
+            foreach ($options as $key => $value) {
+                if (is_null($value) || (is_array($value) && empty($value))) {
+                    unset($options[$key]);
+                }
+            }
+
+            if (is_array($options) && !empty($options)) {
+                $build .= ', ' . self::varExport($options);
+            }
+
+            $build .= ');';
+
+            $result[] = $build;
+        }
+
+        return implode(PHP_EOL, $result);
+    }
+
+    private function buildTemplates(array $templates, array &$build)
+    {
+        foreach ($templates as $name => $options) {
+            if (is_array($options) && !empty($options)) {
+                $build[] = "    \$table->addTemplate('$name', " . self::varExport($options) . ");";
+            } elseif (isset($templates[0])) {
+                $build[] = "    \$table->addTemplate('$options');";
+            } else {
+                $build[] = "    \$table->addTemplate('$name');";
+            }
+        }
+    }
+
+    private function buildActAs($actAs, array &$build)
+    {
+        // rewrite special case of actAs: [Behavior] which gave [0] => Behavior
+        if (is_array($actAs) && isset($actAs[0]) && !is_array($actAs[0])) {
+            $actAs = array_flip($actAs);
+        }
+
+        // rewrite special case of actAs: Behavior
+        if (!is_array($actAs))
+            $actAs = array($actAs => '');
+
+        foreach ($actAs as $template => $options) {
+            // find class matching $name
+            if (class_exists('IPF_ORM_Template_'.$template, true))
+                $classname = 'IPF_ORM_Template_'.$template;
+            else
+                $classname = $template;
+
+            if (is_array($options))
+                $options = self::varExport($options);
+            else
+                $options = '';
+
+            $build[] = "    \$table->addTemplate(new $classname($options));";
+        }
+    }
+
+    private function buildAttributes(array $attributes)
+    {
+        $build = PHP_EOL;
+        foreach ($attributes as $key => $value) {
+
+            if (is_bool($value)) {
+                $values = $value ? 'true':'false';
+            } else {
+                if (!is_array($value))
+                    $value = array($value);
+
+                $values = '';
+                foreach ($value as $attr) {
+                    $values .= "IPF_ORM::" . strtoupper($key) . "_" . strtoupper($attr) . ' ^ ';
+                }
+
+                // Trim last ^
+                $values = substr($values, 0, strlen($values) - 3);
+            }
+
+            $build .= "    \$table->setAttribute(IPF_ORM::ATTR_" . strtoupper($key) . ", " . $values . ");" . PHP_EOL;
+        }
+
+        return $build;
+    }
+
+    private function buildOptions(array $options)
+    {
+        $build = '';
+        foreach ($options as $name => $value) {
+            $build .= "    \$table->setOption('$name', " . self::varExport($value) . ");" . PHP_EOL;
+        }
+        return $build;
+    }
+
+    public function buildRecord(array $definition, $targetPath)
+    {
+        if (!isset($definition['className']))
+            throw new IPF_ORM_Exception('Missing class name.');
+
+        $this->writeBaseDefinition($definition, $targetPath);
+        $this->writeModelDefinition($definition, $targetPath);
+    }
+
+    private function writeBaseDefinition(array $definition, $targetPath)
+    {
+        $code = array(
+            '<?php',
+            '',
+            '/**',
+            ' * This class has been auto-generated by the IPF_ORM Framework.',
+            ' * Changes to this file may cause incorrect behavior',
+            ' * and will be lost if the code is regenerated.',
+            ' */',
+            '',
+        );
+
+        $code[] = 'abstract class '.$this->_baseClassPrefix.$definition['className'].' extends IPF_ORM_Record';
+        $code[] = '{';
+        $code[] = $this->buildTableDefinition($definition);
+        $code[] = '';
+        $code   = array_merge($code, $this->buildSetUp($definition));
+        $code[] = '';
+        $code   = array_merge($code, $this->buildShortcuts($definition));
+        $code[] = '}';
+
+        $fileName = $this->_baseClassPrefix . $definition['className'] . '.php';
+        $writePath = $targetPath . DIRECTORY_SEPARATOR . $this->_baseClassesDirectory;
+        IPF_Utils::makeDirectories($writePath);
+        $writePath .= DIRECTORY_SEPARATOR . $fileName;
+
+        if (file_put_contents($writePath, implode(PHP_EOL, $code)) === false)
+            throw new IPF_ORM_Exception("Couldn't write file " . $writePath);
+    }
+
+    private function buildShortcuts(array $definition)
+    {
+        return array(
+            '  public static function table()',
+            '  {',
+            '    return IPF_ORM::getTable(\''.$definition['className'].'\');',
+            '  }',
+            '',
+            '  public static function query($alias=\'\')',
+            '  {',
+            '    return IPF_ORM::getTable(\''.$definition['className'].'\')->createQuery($alias);',
+            '  }',
+        );
+    }
+
+    private function writeModelDefinition(array $definition, $targetPath)
+    {
+        $className = $definition['className'];
+        $adminClassName = $className.'Admin';
+
+        $writePath = $targetPath . DIRECTORY_SEPARATOR . $className . '.php';
+        if (file_exists($writePath))
+            return;
+
+        $code = array(
+            '<?php',
+            '',
+            sprintf('class %s extends %s%s', $className, $this->_baseClassPrefix, $className),
+            '{',
+            '}',
+            '',
+            '/*',
+            'class '.$adminClassName.' extends IPF_Admin_Model',
+            '{',
+            '}',
+            '',
+            '// Add following line to your admin.php to enable admin interface for this model',
+            "new $adminClassName('$className'),",
+            '*/',
+            '',
+        );
+
+        IPF_Utils::makeDirectories($targetPath);
+        if (file_put_contents($writePath, implode(PHP_EOL, $code)) === false)
+            throw new IPF_ORM_Exception("Couldn't write file " . $writePath);
+    }
+}
+
diff --git a/src/orm/import/schema.php b/src/orm/import/schema.php
new file mode 100644 (file)
index 0000000..889e070
--- /dev/null
@@ -0,0 +1,490 @@
+<?php
+
+class IPF_ORM_Import_Schema
+{
+    protected $_relations = array();
+
+    protected $_validation = array(
+        'root' => array(
+            'abstract',
+            'connection',
+            'className',
+            'tableName',
+            'connection',
+            'relations',
+            'columns',
+            'indexes',
+            'attributes',
+            'templates',
+            'actAs',
+            'options',
+            'inheritance',
+            'detect_relations',
+            'listeners',
+            'ordering',
+        ),
+        'column' => array(
+            'name',
+            'format',
+            'fixed',
+            'primary',
+            'autoincrement',
+            'type',
+            'length',
+            'size',
+            'default',
+            'scale',
+            'values',
+            'comment',
+            'protected',
+            'zerofill',
+            'owner',
+            'exclude',
+            'collate',
+            'charset',
+        ),
+        'relation' => array(
+            'key',
+            'class',
+            'alias',
+            'type',
+            'refClass',
+            'local',
+            'exclude',
+            'foreign',
+            'foreignClass',
+            'foreignAlias',
+            'foreignType',
+            'foreignExclude',
+            'autoComplete',
+            'onDelete',
+            'onUpdate',
+            'equal',
+            'owningSide',
+        ),
+        'inheritance' => array(
+            'type',
+            'extends',
+            'keyField',
+            'keyValue',
+        ),
+    );
+
+    protected $_validators = array();
+
+    public function getValidators()
+    {
+        if (empty($this->_validators)) {
+            $this->_validators = IPF_ORM_Utils::getValidators();
+        }
+        return $this->_validators;
+    }
+
+    public function importSchema($filename, $extraAllwedReferences)
+    {
+        $filename = (array)$filename;
+        $array = array();
+        foreach ($filename as $fn) {
+            $definitions = $this->parseSchema($fn);
+            foreach ($definitions as &$definition)
+                $definition['input_file'] = $fn;
+            $array = array_merge($array, $definitions);
+        }
+
+        $array = $this->_buildRelationships($array, $extraAllwedReferences);
+        $array = $this->_processInheritance($array);
+        return $array;
+    }
+
+    private function parseSchema($schema)
+    {
+        $defaults = array('abstract'            =>  false,
+                          'className'           =>  null,
+                          'tableName'           =>  null,
+                          'connection'          =>  null,
+                          'relations'           =>  array(),
+                          'indexes'             =>  array(),
+                          'attributes'          =>  array(),
+                          'templates'           =>  array(),
+                          'actAs'               =>  array(),
+                          'options'             =>  array(),
+                          'inheritance'         =>  array(),
+                          'detect_relations'    =>  false);
+        
+        $array = Spyc::YAMLLoad($schema);
+
+        // Go through the schema and look for global values so we can assign them to each table/class
+        $globals = array();
+        $globalKeys = array('connection',
+                            'attributes',
+                            'templates',
+                            'actAs',
+                            'options',
+                            'inheritance',
+                            'detect_relations');
+
+        // Loop over and build up all the global values and remove them from the array
+        foreach ($array as $key => $value) {
+            if (in_array($key, $globalKeys)) {
+                unset($array[$key]);
+                $globals[$key] = $value;
+            }
+        }
+
+        // Apply the globals to each table if it does not have a custom value set already
+        foreach ($array as $className => $table) {
+            foreach ($globals as $key => $value) {
+                if (!isset($array[$className][$key])) {
+                    $array[$className][$key] = $value;
+                }
+            }
+        }
+
+        $build = array();
+
+        foreach ($array as $className => $table) {
+            $this->_validateSchemaElement('root', array_keys($table), $className);
+
+            $columns = array();
+
+            $className = isset($table['className']) ? (string) $table['className']:(string) $className;
+
+            if (isset($table['inheritance']['keyField']) || isset($table['inheritance']['keyValue'])) {
+                $table['inheritance']['type'] = 'column_aggregation';
+            }
+
+            if (isset($table['tableName']) && $table['tableName']) {
+                $tableName = $table['tableName'];
+            } else {
+                if (isset($table['inheritance']['type']) && ($table['inheritance']['type'] == 'column_aggregation')) {
+                    $tableName = null;
+                } else {
+                    $tableName = IPF_ORM_Inflector::tableize($className);
+                }
+            }
+
+            $connection = isset($table['connection']) ? $table['connection']:'current';
+
+            $columns = isset($table['columns']) ? $table['columns']:array();
+
+            if ( ! empty($columns)) {
+                foreach ($columns as $columnName => $field) {
+
+                    // Support short syntax: my_column: integer(4)
+                    if ( ! is_array($field)) {
+                        $original = $field;
+                        $field = array();
+                        $field['type'] = $original;
+                    }
+
+                    $colDesc = array();
+                    if (isset($field['name'])) {
+                        $colDesc['name'] = $field['name'];
+                    } else {
+                        $colDesc['name'] = $columnName;
+                    }
+
+                    $this->_validateSchemaElement('column', array_keys($field), $className . '->columns->' . $colDesc['name']);
+
+                    // Support short type(length) syntax: my_column: { type: integer(4) }
+                    $e = explode('(', $field['type']);
+                    if (isset($e[0]) && isset($e[1])) {
+                        $colDesc['type'] = $e[0];
+                        $colDesc['length'] = substr($e[1], 0, strlen($e[1]) - 1);
+                    } else {
+                        $colDesc['type'] = isset($field['type']) ? (string) $field['type']:null;
+                        $colDesc['length'] = isset($field['length']) ? (int) $field['length']:null;
+                        $colDesc['length'] = isset($field['size']) ? (int) $field['size']:$colDesc['length'];
+                    }
+
+                    $colDesc['fixed'] = isset($field['fixed']) ? (int) $field['fixed']:null;
+                    $colDesc['primary'] = isset($field['primary']) ? (bool) (isset($field['primary']) && $field['primary']):null;
+                    $colDesc['default'] = isset($field['default']) ? $field['default']:null;
+                    $colDesc['autoincrement'] = isset($field['autoincrement']) ? (bool) (isset($field['autoincrement']) && $field['autoincrement']):null;
+                    $colDesc['values'] = isset($field['values']) ? (array) $field['values']:null;
+
+                    // Include all the specified and valid validators in the colDesc
+                    $validators = $this->getValidators();
+
+                    foreach ($validators as $validator) {
+                        if (isset($field[$validator])) {
+                            $colDesc[$validator] = $field[$validator];
+                        }
+                    }
+
+                    $columns[(string) $columnName] = $colDesc;
+                }
+            }
+
+            // Apply the default values
+            foreach ($defaults as $key => $defaultValue) {
+                if (isset($table[$key]) && ! isset($build[$className][$key])) {
+                    $build[$className][$key] = $table[$key];
+                } else {
+                    $build[$className][$key] = isset($build[$className][$key]) ? $build[$className][$key]:$defaultValue;
+                }
+            }
+            
+            $build[$className]['className'] = $className;
+            $build[$className]['tableName'] = $tableName;
+            $build[$className]['columns'] = $columns;
+            
+            // Make sure that anything else that is specified in the schema makes it to the final array
+            $build[$className] = IPF_ORM_Utils::arrayDeepMerge($table, $build[$className]);
+            
+            // We need to keep track of the className for the connection
+            $build[$className]['connectionClassName'] = $build[$className]['className'];
+        }
+
+        return $build;
+    }
+
+    protected function _processInheritance($array)
+    {
+        // Apply default inheritance configuration
+        foreach ($array as $className => $definition) {
+            if ( ! empty($array[$className]['inheritance'])) {
+                $this->_validateSchemaElement('inheritance', array_keys($definition['inheritance']), $className . '->inheritance');
+
+                // Default inheritance to concrete inheritance
+                if ( ! isset($array[$className]['inheritance']['type'])) {
+                    $array[$className]['inheritance']['type'] = 'concrete';
+                }
+
+                // Some magic for setting up the keyField and keyValue column aggregation options
+                // Adds keyField to the parent class automatically
+                if ($array[$className]['inheritance']['type'] == 'column_aggregation') {
+                    // Set the keyField to 'type' by default
+                    if ( ! isset($array[$className]['inheritance']['keyField'])) {
+                        $array[$className]['inheritance']['keyField'] = 'type';                        
+                    }
+                    
+                    // Set the keyValue to the name of the child class if it does not exist
+                    if ( ! isset($array[$className]['inheritance']['keyValue'])) {
+                        $array[$className]['inheritance']['keyValue'] = $className;
+                    }
+                    
+                    // Add the keyType column to the parent if a definition does not already exist
+                    if ( ! isset($array[$array[$className]['inheritance']['extends']]['columns'][$array[$className]['inheritance']['keyField']])) {
+                        $array[$definition['inheritance']['extends']]['columns'][$array[$className]['inheritance']['keyField']] = array('name' => $array[$className]['inheritance']['keyField'], 'type' => 'string', 'length' => 255);
+                    }
+                }
+            }
+        }
+
+        // Array of the array keys to move to the parent, and the value to default the child definition to
+        // after moving it. Will also populate the subclasses array for the inheritance parent
+        $moves = array('columns' => array());
+        
+        foreach ($array as $className => $definition) {
+            if (!isset($definition['className']))
+                continue;
+            $parent = $this->_findBaseSuperClass($array, $definition['className']);
+            // Move any definitions on the schema to the parent
+            if (isset($definition['inheritance']['extends']) && isset($definition['inheritance']['type']) && ($definition['inheritance']['type'] == 'simple' || $definition['inheritance']['type'] == 'column_aggregation')) {
+                foreach ($moves as $move => $resetValue) {
+                    $array[$parent][$move] = IPF_ORM_Utils::arrayDeepMerge($array[$parent][$move], $definition[$move]);
+                    $array[$definition['className']][$move] = $resetValue;
+                }
+
+                // Populate the parents subclasses
+                if ($definition['inheritance']['type'] == 'column_aggregation') {
+                    $array[$parent]['inheritance']['subclasses'][$definition['className']] = array($definition['inheritance']['keyField'] => $definition['inheritance']['keyValue']);
+                }
+            }
+        }
+
+        return $array;
+    }
+
+    protected function _findBaseSuperClass($array, $class)
+    {
+        if (isset($array[$class]['inheritance']['extends'])) {
+            return $this->_findBaseSuperClass($array, $array[$class]['inheritance']['extends']);
+        } else {
+            return $class;
+        }
+    }
+
+    protected function _buildRelationships($array, $extraAllwedReferences)
+    {
+        // Handle auto detecting relations by the names of columns
+        // User.contact_id will automatically create User hasOne Contact local => contact_id, foreign => id
+        foreach ($array as $className => $properties) {
+            if (isset($properties['columns']) && ! empty($properties['columns']) && isset($properties['detect_relations']) && $properties['detect_relations']) {
+                foreach ($properties['columns'] as $column) {
+                    // Check if the column we are inflecting has a _id on the end of it before trying to inflect it and find
+                    // the class name for the column
+                    if (strpos($column['name'], '_id')) {
+                        $columnClassName = IPF_ORM_Inflector::classify(str_replace('_id', '', $column['name']));
+                        if (isset($array[$columnClassName]) && !isset($array[$className]['relations'][$columnClassName])) {
+                            $array[$className]['relations'][$columnClassName] = array();
+
+                            // Set the detected foreign key type and length to the same as the primary key
+                            // of the related table
+                            $type = isset($array[$columnClassName]['columns']['id']['type']) ? $array[$columnClassName]['columns']['id']['type']:'integer';
+                            $length = isset($array[$columnClassName]['columns']['id']['length']) ? $array[$columnClassName]['columns']['id']['length']:8;
+                            $array[$className]['columns'][$column['name']]['type'] = $type;
+                            $array[$className]['columns'][$column['name']]['length'] = $length;
+                        }
+                    }
+                }
+            }
+        }
+
+        foreach ($array as $name => $properties) {
+            if (!isset($properties['relations'])) {
+                continue;
+            }
+
+            $className = $properties['className'];
+            $relations = $properties['relations'];
+
+            foreach ($relations as $alias => $relation) {
+                $class = isset($relation['class']) ? $relation['class'] : $alias;
+                if (!isset($array[$class]) && !in_array($class, $extraAllwedReferences)) {
+                    print "Warning: Ignoring relation to unknown model '$class' in model '$className'. \n";
+                    continue;
+                }
+                $relation['class'] = $class;
+                $relation['alias'] = isset($relation['alias']) ? $relation['alias'] : $alias;
+                
+                // Attempt to guess the local and foreign
+                if (isset($relation['refClass'])) {
+                    $relation['local'] = isset($relation['local']) ? $relation['local']:IPF_ORM_Inflector::tableize($name) . '_id';
+                    $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:IPF_ORM_Inflector::tableize($class) . '_id';
+                } else {
+                    $relation['local'] = isset($relation['local']) ? $relation['local']:IPF_ORM_Inflector::tableize($relation['class']) . '_id';
+                    $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:'id';
+                }
+                
+                if (isset($relation['refClass'])) {
+                    $relation['type'] = 'many';
+                }
+                
+                if (isset($relation['type']) && $relation['type']) {
+                    $relation['type'] = $relation['type'] === 'one' ? IPF_ORM_Relation::ONE:IPF_ORM_Relation::MANY;
+                } else {
+                    $relation['type'] = IPF_ORM_Relation::ONE;
+                }
+
+                if (isset($relation['foreignType']) && $relation['foreignType']) {
+                    $relation['foreignType'] = $relation['foreignType'] === 'one' ? IPF_ORM_Relation::ONE:IPF_ORM_Relation::MANY;
+                }
+                
+                $relation['key'] = $this->_buildUniqueRelationKey($relation);
+
+                $this->_validateSchemaElement('relation', array_keys($relation), $className . '->relation->' . $relation['alias']);
+                
+                $this->_relations[$className][$alias] = $relation;
+            }
+        }
+
+        // Now we auto-complete opposite ends of relationships
+        $this->_autoCompleteOppositeRelations($extraAllwedReferences);
+
+        // Make sure we do not have any duplicate relations
+        $this->_fixDuplicateRelations();
+
+        //$array['relations'];
+        // Set the full array of relationships for each class to the final array
+        foreach ($this->_relations as $className => $relations) {
+            if (!in_array($className, $extraAllwedReferences))
+                $array[$className]['relations'] = $relations;
+        }
+
+        return $array;
+    }
+
+    protected function _autoCompleteOppositeRelations($extraAllwedReferences)
+    {
+        foreach ($this->_relations as $className => $relations) {
+            if (in_array($className, $extraAllwedReferences))
+                continue;
+
+            foreach ($relations as $alias => $relation) {
+                if ((isset($relation['equal']) && $relation['equal']) || (isset($relation['autoComplete']) && $relation['autoComplete'] === false)) {
+                    continue;
+                }
+                
+                $newRelation = array();
+                $newRelation['foreign'] = $relation['local'];
+                $newRelation['local'] = $relation['foreign'];
+                $newRelation['class'] = isset($relation['foreignClass']) ? $relation['foreignClass'] : $className;
+                $newRelation['alias'] = isset($relation['foreignAlias']) ? $relation['foreignAlias'] : $className;
+                $newRelation['exclude'] = isset($relation['foreignExclude']) ? $relation['foreignExclude'] : false;
+
+                // this is so that we know that this relation was autogenerated and
+                // that we do not need to include it if it is explicitly declared in the schema by the users.
+                $newRelation['autogenerated'] = true; 
+
+                if (isset($relation['refClass'])) {
+                    $newRelation['refClass'] = $relation['refClass'];
+                    $newRelation['type'] = isset($relation['foreignType']) ? $relation['foreignType']:$relation['type'];
+                } else {                
+                    if(isset($relation['foreignType'])) {
+                        $newRelation['type'] = $relation['foreignType'];
+                    } else {
+                        $newRelation['type'] = $relation['type'] === IPF_ORM_Relation::ONE ? IPF_ORM_Relation::MANY:IPF_ORM_Relation::ONE;
+                    }
+                }
+
+                // Make sure it doesn't already exist
+                if ( ! isset($this->_relations[$relation['class']][$newRelation['alias']])) {
+                    $newRelation['key'] = $this->_buildUniqueRelationKey($newRelation);
+                    $this->_relations[$relation['class']][$newRelation['alias']] = $newRelation;
+                }
+            }
+        }
+    }
+
+    protected function _fixDuplicateRelations()
+    {
+        foreach($this->_relations as $className => $relations) {
+            // This is for checking for duplicates between alias-relations and a auto-generated relations to ensure the result set of unique relations
+            $existingRelations = array();
+            $uniqueRelations = array();
+            foreach ($relations as $relation) {
+                if ( ! in_array($relation['key'], $existingRelations)) {
+                    $existingRelations[] = $relation['key'];
+                    $uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation));
+                } else {
+                    // check to see if this relationship is not autogenerated, if it's not, then the user must have explicitly declared it
+                    if ( ! isset($relation['autogenerated']) || $relation['autogenerated'] != true) {
+                        $uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation));
+                    }
+                }
+            }
+            
+            $this->_relations[$className] = $uniqueRelations;
+        }
+    }
+
+    protected function _buildUniqueRelationKey($relation)
+    {
+        return md5($relation['local'].$relation['foreign'].$relation['class'].(isset($relation['refClass']) ? $relation['refClass']:null));
+    }
+
+    protected function _validateSchemaElement($name, $element, $path)
+    {
+        $element = (array) $element;
+
+        $validation = $this->_validation[$name];
+
+        // Validators are a part of the column validation
+        // This should be fixed, made cleaner
+        if ($name == 'column') {
+            $validators = $this->getValidators();
+            $validation = array_merge($validation, $validators);
+        }
+
+        $validation = array_flip($validation);
+        foreach ($element as $key => $value) {
+            if ( ! isset($validation[$value])) {
+                throw new IPF_ORM_Exception(sprintf('Invalid schema element named "' . $value .
+                    '" at path "' . $path . '"'));
+            }
+        }
+    }
+}
+
diff --git a/src/orm/inflector.php b/src/orm/inflector.php
new file mode 100644 (file)
index 0000000..4946ac5
--- /dev/null
@@ -0,0 +1,341 @@
+<?php
+
+class IPF_ORM_Inflector
+{
+    public static function pluralize($word)
+    {
+        $plural = array('/(quiz)$/i' => '\1zes',
+                        '/^(ox)$/i' => '\1en',
+                        '/([m|l])ouse$/i' => '\1ice',
+                        '/(matr|vert|ind)ix|ex$/i' => '\1ices',
+                        '/(x|ch|ss|sh)$/i' => '\1es',
+                        '/([^aeiouy]|qu)ies$/i' => '\1y',
+                        '/([^aeiouy]|qu)y$/i' => '\1ies',
+                        '/(hive)$/i' => '\1s',
+                        '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
+                        '/sis$/i' => 'ses',
+                        '/([ti])um$/i' => '\1a',
+                        '/(buffal|tomat)o$/i' => '\1oes',
+                        '/(bu)s$/i' => '\1ses',
+                        '/(alias|status)/i' => '\1es',
+                        '/(octop|vir)us$/i' => '\1i',
+                        '/(ax|test)is$/i' => '\1es',
+                        '/s$/i' => 's',
+                        '/$/' => 's');
+
+        $uncountable = array('equipment',
+                             'information',
+                             'rice',
+                             'money',
+                             'species',
+                             'series',
+                             'fish',
+                             'sheep');
+
+        $irregular = array('person' => 'people',
+                           'man'    => 'men',
+                           'child'  => 'children',
+                           'sex'    => 'sexes',
+                           'move'   => 'moves');
+
+        $lowercasedWord = strtolower($word);
+
+        foreach ($uncountable as $_uncountable) {
+            if (substr($lowercasedWord, (-1 * strlen($_uncountable))) == $_uncountable) {
+                return $word;
+            }
+        }
+
+        foreach ($irregular as $_plural=> $_singular) {
+            if (preg_match('/('.$_plural.')$/i', $word, $arr)) {
+                return preg_replace('/('.$_plural.')$/i', substr($arr[0],0,1) . substr($_singular,1), $word);
+            }
+        }
+
+        foreach ($plural as $rule => $replacement) {
+            if (preg_match($rule, $word)) {
+                return preg_replace($rule, $replacement, $word);
+            }
+        }
+
+        return false;
+    }
+
+    public static function singularize($word)
+    {
+        $singular = array('/(quiz)zes$/i' => '\\1',
+                          '/(matr)ices$/i' => '\\1ix',
+                          '/(vert|ind)ices$/i' => '\\1ex',
+                          '/^(ox)en/i' => '\\1',
+                          '/(alias|status)es$/i' => '\\1',
+                          '/([octop|vir])i$/i' => '\\1us',
+                          '/(cris|ax|test)es$/i' => '\\1is',
+                          '/(shoe)s$/i' => '\\1',
+                          '/(o)es$/i' => '\\1',
+                          '/(bus)es$/i' => '\\1',
+                          '/([m|l])ice$/i' => '\\1ouse',
+                          '/(x|ch|ss|sh)es$/i' => '\\1',
+                          '/(m)ovies$/i' => '\\1ovie',
+                          '/(s)eries$/i' => '\\1eries',
+                          '/([^aeiouy]|qu)ies$/i' => '\\1y',
+                          '/([lr])ves$/i' => '\\1f',
+                          '/(tive)s$/i' => '\\1',
+                          '/(hive)s$/i' => '\\1',
+                          '/([^f])ves$/i' => '\\1fe',
+                          '/(^analy)ses$/i' => '\\1sis',
+                          '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\\1\\2sis',
+                          '/([ti])a$/i' => '\\1um',
+                          '/(n)ews$/i' => '\\1ews',
+                          '/^(.{2,2})$/i' => '\\1',
+                          '/s$/i' => '');
+
+        $uncountable = array('equipment',
+                             'information',
+                             'rice',
+                             'money',
+                             'species',
+                             'series',
+                             'fish',
+                             'sheep',
+                             'sms',
+                             'status',
+                             'access');
+
+        $irregular = array('person' => 'people',
+                           'man'    => 'men',
+                           'child'  => 'children',
+                           'sex'    => 'sexes',
+                           'move'   => 'moves');
+
+        $lowercasedWord = strtolower($word);
+        foreach ($uncountable as $_uncountable) {
+            if (substr($lowercasedWord, ( -1 * strlen($_uncountable))) == $_uncountable) {
+                return $word;
+            }
+        }
+
+        foreach ($irregular as $_singular => $_plural) {
+            if (preg_match('/('.$_plural.')$/i', $word, $arr)) {
+                return preg_replace('/('.$_plural.')$/i', substr($arr[0],0,1).substr($_singular,1), $word);
+            }
+        }
+
+        foreach ($singular as $rule => $replacement) {
+            if (preg_match($rule, $word)) {
+                return preg_replace($rule, $replacement, $word);
+            }
+        }
+
+        return $word;
+    }
+
+    public static function tableize($word)
+    {
+        // Would prefer this but it breaks unit tests. Forces the table underscore pattern
+        // return self::pluralize(self::underscore($name));
+        return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word));
+    }
+
+    public static function classify($word)
+    {
+        return preg_replace_callback('~(_?)(_)([\w])~', array("IPF_ORM_Inflector", "classifyCallback"), ucfirst(strtolower($word)));
+    }
+
+    public static function classifyCallback($matches)
+    {
+        return $matches[1] . strtoupper($matches[3]);
+    }
+
+    public static function seemsUtf8($string)
+    {
+        for ($i = 0; $i < strlen($string); $i++) {
+            if (ord($string[$i]) < 0x80) continue; # 0bbbbbbb
+            elseif ((ord($string[$i]) & 0xE0) == 0xC0) $n=1; # 110bbbbb
+            elseif ((ord($string[$i]) & 0xF0) == 0xE0) $n=2; # 1110bbbb
+            elseif ((ord($string[$i]) & 0xF8) == 0xF0) $n=3; # 11110bbb
+            elseif ((ord($string[$i]) & 0xFC) == 0xF8) $n=4; # 111110bb
+            elseif ((ord($string[$i]) & 0xFE) == 0xFC) $n=5; # 1111110b
+            else return false; # Does not match any model
+            for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ?
+                if ((++$i == strlen($string)) || ((ord($string[$i]) & 0xC0) != 0x80))
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static function unaccent($string)
+    {
+        if (!preg_match('/[\x80-\xff]/', $string)) {
+            return $string;
+        }
+
+        if (self::seemsUtf8($string)) {
+            $chars = array(
+            // Decompositions for Latin-1 Supplement
+            chr(195).chr(128) => 'A', chr(195).chr(129) => 'A',
+            chr(195).chr(130) => 'A', chr(195).chr(131) => 'A',
+            chr(195).chr(132) => 'A', chr(195).chr(133) => 'A',
+            chr(195).chr(135) => 'C', chr(195).chr(136) => 'E',
+            chr(195).chr(137) => 'E', chr(195).chr(138) => 'E',
+            chr(195).chr(139) => 'E', chr(195).chr(140) => 'I',
+            chr(195).chr(141) => 'I', chr(195).chr(142) => 'I',
+            chr(195).chr(143) => 'I', chr(195).chr(145) => 'N',
+            chr(195).chr(146) => 'O', chr(195).chr(147) => 'O',
+            chr(195).chr(148) => 'O', chr(195).chr(149) => 'O',
+            chr(195).chr(150) => 'O', chr(195).chr(153) => 'U',
+            chr(195).chr(154) => 'U', chr(195).chr(155) => 'U',
+            chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y',
+            chr(195).chr(159) => 's', chr(195).chr(160) => 'a',
+            chr(195).chr(161) => 'a', chr(195).chr(162) => 'a',
+            chr(195).chr(163) => 'a', chr(195).chr(164) => 'a',
+            chr(195).chr(165) => 'a', chr(195).chr(167) => 'c',
+            chr(195).chr(168) => 'e', chr(195).chr(169) => 'e',
+            chr(195).chr(170) => 'e', chr(195).chr(171) => 'e',
+            chr(195).chr(172) => 'i', chr(195).chr(173) => 'i',
+            chr(195).chr(174) => 'i', chr(195).chr(175) => 'i',
+            chr(195).chr(177) => 'n', chr(195).chr(178) => 'o',
+            chr(195).chr(179) => 'o', chr(195).chr(180) => 'o',
+            chr(195).chr(181) => 'o', chr(195).chr(182) => 'o',
+            chr(195).chr(182) => 'o', chr(195).chr(185) => 'u',
+            chr(195).chr(186) => 'u', chr(195).chr(187) => 'u',
+            chr(195).chr(188) => 'u', chr(195).chr(189) => 'y',
+            chr(195).chr(191) => 'y',
+            // Decompositions for Latin Extended-A
+            chr(196).chr(128) => 'A', chr(196).chr(129) => 'a',
+            chr(196).chr(130) => 'A', chr(196).chr(131) => 'a',
+            chr(196).chr(132) => 'A', chr(196).chr(133) => 'a',
+            chr(196).chr(134) => 'C', chr(196).chr(135) => 'c',
+            chr(196).chr(136) => 'C', chr(196).chr(137) => 'c',
+            chr(196).chr(138) => 'C', chr(196).chr(139) => 'c',
+            chr(196).chr(140) => 'C', chr(196).chr(141) => 'c',
+            chr(196).chr(142) => 'D', chr(196).chr(143) => 'd',
+            chr(196).chr(144) => 'D', chr(196).chr(145) => 'd',
+            chr(196).chr(146) => 'E', chr(196).chr(147) => 'e',
+            chr(196).chr(148) => 'E', chr(196).chr(149) => 'e',
+            chr(196).chr(150) => 'E', chr(196).chr(151) => 'e',
+            chr(196).chr(152) => 'E', chr(196).chr(153) => 'e',
+            chr(196).chr(154) => 'E', chr(196).chr(155) => 'e',
+            chr(196).chr(156) => 'G', chr(196).chr(157) => 'g',
+            chr(196).chr(158) => 'G', chr(196).chr(159) => 'g',
+            chr(196).chr(160) => 'G', chr(196).chr(161) => 'g',
+            chr(196).chr(162) => 'G', chr(196).chr(163) => 'g',
+            chr(196).chr(164) => 'H', chr(196).chr(165) => 'h',
+            chr(196).chr(166) => 'H', chr(196).chr(167) => 'h',
+            chr(196).chr(168) => 'I', chr(196).chr(169) => 'i',
+            chr(196).chr(170) => 'I', chr(196).chr(171) => 'i',
+            chr(196).chr(172) => 'I', chr(196).chr(173) => 'i',
+            chr(196).chr(174) => 'I', chr(196).chr(175) => 'i',
+            chr(196).chr(176) => 'I', chr(196).chr(177) => 'i',
+            chr(196).chr(178) => 'IJ',chr(196).chr(179) => 'ij',
+            chr(196).chr(180) => 'J', chr(196).chr(181) => 'j',
+            chr(196).chr(182) => 'K', chr(196).chr(183) => 'k',
+            chr(196).chr(184) => 'k', chr(196).chr(185) => 'L',
+            chr(196).chr(186) => 'l', chr(196).chr(187) => 'L',
+            chr(196).chr(188) => 'l', chr(196).chr(189) => 'L',
+            chr(196).chr(190) => 'l', chr(196).chr(191) => 'L',
+            chr(197).chr(128) => 'l', chr(197).chr(129) => 'L',
+            chr(197).chr(130) => 'l', chr(197).chr(131) => 'N',
+            chr(197).chr(132) => 'n', chr(197).chr(133) => 'N',
+            chr(197).chr(134) => 'n', chr(197).chr(135) => 'N',
+            chr(197).chr(136) => 'n', chr(197).chr(137) => 'N',
+            chr(197).chr(138) => 'n', chr(197).chr(139) => 'N',
+            chr(197).chr(140) => 'O', chr(197).chr(141) => 'o',
+            chr(197).chr(142) => 'O', chr(197).chr(143) => 'o',
+            chr(197).chr(144) => 'O', chr(197).chr(145) => 'o',
+            chr(197).chr(146) => 'OE',chr(197).chr(147) => 'oe',
+            chr(197).chr(148) => 'R', chr(197).chr(149) => 'r',
+            chr(197).chr(150) => 'R', chr(197).chr(151) => 'r',
+            chr(197).chr(152) => 'R', chr(197).chr(153) => 'r',
+            chr(197).chr(154) => 'S', chr(197).chr(155) => 's',
+            chr(197).chr(156) => 'S', chr(197).chr(157) => 's',
+            chr(197).chr(158) => 'S', chr(197).chr(159) => 's',
+            chr(197).chr(160) => 'S', chr(197).chr(161) => 's',
+            chr(197).chr(162) => 'T', chr(197).chr(163) => 't',
+            chr(197).chr(164) => 'T', chr(197).chr(165) => 't',
+            chr(197).chr(166) => 'T', chr(197).chr(167) => 't',
+            chr(197).chr(168) => 'U', chr(197).chr(169) => 'u',
+            chr(197).chr(170) => 'U', chr(197).chr(171) => 'u',
+            chr(197).chr(172) => 'U', chr(197).chr(173) => 'u',
+            chr(197).chr(174) => 'U', chr(197).chr(175) => 'u',
+            chr(197).chr(176) => 'U', chr(197).chr(177) => 'u',
+            chr(197).chr(178) => 'U', chr(197).chr(179) => 'u',
+            chr(197).chr(180) => 'W', chr(197).chr(181) => 'w',
+            chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y',
+            chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z',
+            chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z',
+            chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z',
+            chr(197).chr(190) => 'z', chr(197).chr(191) => 's',
+            // Euro Sign
+            chr(226).chr(130).chr(172) => 'E',
+            // GBP (Pound) Sign
+            chr(194).chr(163) => '');
+
+            $string = strtr($string, $chars);
+        } else {
+            // Assume ISO-8859-1 if not UTF-8
+            $chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158)
+                .chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194)
+                .chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202)
+                .chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210)
+                .chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218)
+                .chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227)
+                .chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235)
+                .chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243)
+                .chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251)
+                .chr(252).chr(253).chr(255);
+
+            $chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy";
+
+            $string = strtr($string, $chars['in'], $chars['out']);
+            $doubleChars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254));
+            $doubleChars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th');
+            $string = str_replace($doubleChars['in'], $doubleChars['out'], $string);
+        }
+
+        return $string;
+    }
+
+    public static function translit($st)
+    {
+        $st = strtr($st, array(
+            'а' => 'a',  'б' => 'b',    'в' => 'v',  'г' => 'g',  'д' => 'd',
+            'е' => 'e',  'ё' => 'e',    'ж' => 'zh', 'з' => 'z',  'и' => 'i',
+            'й' => 'y',  'к' => 'k',    'л' => 'l',  'м' => 'm',  'н' => 'n',
+            'о' => 'o',  'п' => 'p',    'р' => 'r',  'с' => 's',  'т' => 't',
+            'у' => 'u',  'ф' => 'f',    'х' => 'h',  'ц' => 'ts', 'ч' => 'ch',
+            'ш' => 'sh', 'щ' => 'shch', 'ъ' => '\'', 'ы' => 'i',  'ь' => '',
+            'э' => 'e',  'ю' => 'yu',   'я' => 'ya', 'ї' => 'i',  'є' => 'ie',
+
+            'А' => 'A',  'Б' => 'B',    'В' => 'V',  'Г' => 'G',  'Д' => 'D',
+            'Е' => 'E',  'Ё' => 'E',    'Ж' => 'Zh', 'З' => 'Z',  'И' => 'I',
+            'Й' => 'Y',  'К' => 'K',    'Л' => 'L',  'М' => 'M',  'Н' => 'N',
+            'О' => 'O',  'П' => 'P',    'Р' => 'R',  'С' => 'S',  'Т' => 'T',
+            'У' => 'U',  'Ф' => 'F',    'Х' => 'H',  'Ц' => 'Ts', 'Ч' => 'Ch',
+            'Ш' => 'Sh', 'Щ' => 'Shch', 'Ъ' => '\'', 'Ы' => 'I',  'Ь' => '',
+            'Э' => 'E',  'Ю' => 'Yu',   'Я' => 'Ya', 'Ї' => 'Yi', 'Є' => 'Ye',
+        ));
+        return $st;
+    }
+
+    public static function urlize($text)
+    {
+        // Remove all non url friendly characters with the unaccent function
+        $text = self::unaccent($text);
+        
+        // Transliterate cyrillic
+        $text = self::translit($text);
+
+        // Remove all none word characters
+        $text = preg_replace('/\W/', ' ', $text);
+        
+        // More stripping. Replace spaces with dashes
+        $text = strtolower(preg_replace('/[^A-Z^a-z^0-9^\/]+/', '-',
+                           preg_replace('/([a-z\d])([A-Z])/', '\1_\2',
+                           preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2',
+                           preg_replace('/::/', '/', $text)))));
+        
+        return trim($text, '-');
+    }
+}
+
diff --git a/src/orm/manager.php b/src/orm/manager.php
new file mode 100644 (file)
index 0000000..570de33
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+class IPF_ORM_Manager extends IPF_ORM_Configurable
+{
+    protected $_connection = null;
+    protected $_queryRegistry;
+    public $dbListeners = array();
+
+    private function __construct()
+    {
+        $this->attributes = array(
+            IPF_ORM::ATTR_LOAD_REFERENCES         => true,
+            IPF_ORM::ATTR_PORTABILITY             => IPF_ORM::PORTABILITY_ALL,
+            IPF_ORM::ATTR_EXPORT                  => IPF_ORM::EXPORT_ALL,
+            IPF_ORM::ATTR_DECIMAL_PLACES          => 2,
+            IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE => 'ipf',
+        );
+    }
+
+    public static function getInstance()
+    {
+        static $instance;
+        if ( ! isset($instance)) {
+            $instance = new self();
+        }
+        return $instance;
+    }
+
+    public function getQueryRegistry()
+    {
+        if ( ! isset($this->_queryRegistry)) {
+            $this->_queryRegistry = new IPF_ORM_Query_Registry;
+        }
+        return $this->_queryRegistry;
+    }
+
+    public function setQueryRegistry(IPF_ORM_Query_Registry $registry)
+    {
+        $this->_queryRegistry = $registry;
+        
+        return $this;
+    }
+
+    public static function connection()
+    {
+        return IPF_ORM_Manager::getInstance()->getCurrentConnection();
+    }
+
+    public function getCurrentConnection()
+    {
+        if (!$this->_connection) {
+            $pdo = \PFF\Container::databaseConnection();
+            $this->_connection = new IPF_ORM_Connection_Mysql($this, $pdo);
+        }
+        return $this->_connection;
+    }
+
+    public function __toString()
+    {
+        return "<pre>\nIPF_ORM_Manager\n</pre>";
+    }
+}
+
diff --git a/src/orm/null.php b/src/orm/null.php
new file mode 100644 (file)
index 0000000..6347618
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+final class IPF_ORM_Null
+{
+    private static $instance = null;
+    public static function getInstance()
+    {
+        if (!self::$instance)
+            self::$instance = new IPF_ORM_Null;
+        return self::$instance;
+    }
+
+    private function __construct()
+    {
+    }
+
+    private function __clone()
+    {
+    }
+
+    public static function isNull($obj)
+    {
+        return $obj === self::$instance;
+    }
+
+    public function exists()
+    {
+        return false;
+    }
+
+    public function __toString()
+    {
+        return '';
+    }
+}
+
diff --git a/src/orm/pager.php b/src/orm/pager.php
new file mode 100644 (file)
index 0000000..744f44e
--- /dev/null
@@ -0,0 +1,265 @@
+<?php
+
+class IPF_ORM_Pager
+{
+    protected $_query;
+    protected $_countQuery;
+    protected $_countQueryParams;
+    protected $_numResults;
+    protected $_maxPerPage;
+    protected $_page;
+    protected $_lastPage;
+    protected $_executed;
+
+    public function __construct($query, $page, $maxPerPage = 0)
+    {
+        $this->_setExecuted(false);
+
+        $this->_setQuery($query);
+        $this->_setPage($page);
+
+        $this->setMaxPerPage($maxPerPage);
+    }
+
+    protected function _initialize($params = array())
+    {
+        // retrieve the number of items found
+        $count = $this->getCountQuery()->count($this->getCountQueryParams($params));
+        
+        $this->_setNumResults($count);
+        $this->_setExecuted(true); // _adjustOffset relies of _executed equals true = getNumResults()
+
+        $this->_adjustOffset();
+    }
+
+    protected function _adjustOffset()
+    {
+        // Define new total of pages
+        $this->_setLastPage(
+            max(1, ceil($this->getNumResults() / $this->getMaxPerPage()))
+        );
+        $offset = ($this->getPage() - 1) * $this->getMaxPerPage();
+
+        // Assign new offset and limit to IPF_ORM_Query object
+        $p = $this->getQuery();
+        $p->offset($offset);
+        $p->limit($this->getMaxPerPage());
+    }
+
+    public function getExecuted()
+    {
+        return $this->_executed;
+    }
+
+    protected function _setExecuted($executed)
+    {
+        $this->_executed = $executed;
+    }
+
+    public function getRange($rangeStyle, $options = array())
+    {
+        $class = 'IPF_ORM_Pager_Range_' . ucfirst($rangeStyle);
+        return new $class($options, $this);
+    }
+
+    public function getNumResults()
+    {
+        if ($this->getExecuted()) {
+            return $this->_numResults;
+        }
+        throw new IPF_ORM_Exception(
+            'Cannot retrieve the number of results of a not yet executed Pager query'
+        );
+    }
+
+    protected function _setNumResults($nb)
+    {
+        $this->_numResults = $nb;
+    }
+
+    public function getFirstPage()
+    {
+        return 1;
+    }
+
+    public function getLastPage()
+    {
+        if ($this->getExecuted()) {
+            return $this->_lastPage;
+        }
+
+        throw new IPF_ORM_Exception(
+            'Cannot retrieve the last page number of a not yet executed Pager query'
+        );
+    }
+
+    protected function _setLastPage($page)
+    {
+        $this->_lastPage = $page;
+
+        if ($this->getPage() > $page) {
+            $this->_setPage($page);
+        }
+    }
+
+    public function getPage()
+    {
+        return $this->_page;
+    }
+
+    public function getNextPage()
+    {
+        if ($this->getExecuted()) {
+            return min($this->getPage() + 1, $this->getLastPage());
+        }
+
+        throw new IPF_ORM_Exception(
+            'Cannot retrieve the last page number of a not yet executed Pager query'
+        );
+    }
+
+    public function getPreviousPage()
+    {
+        if ($this->getExecuted()) {
+            return max($this->getPage() - 1, $this->getFirstPage());
+        }
+
+        throw new IPF_ORM_Exception(
+            'Cannot retrieve the previous page number of a not yet executed Pager query'
+        );
+    }
+
+    public function getFirstIndice()
+    {
+        return ($this->getPage() - 1) * $this->getMaxPerPage() + 1;
+    }
+
+    public function getLastIndice()
+    {
+        return min($this->getNumResults(), ($this->getPage() * $this->getMaxPerPage()));
+    }
+
+    public function haveToPaginate()
+    {
+        if ($this->getExecuted()) {
+            return $this->getNumResults() > $this->getMaxPerPage();
+        }
+
+        throw new IPF_ORM_Exception(
+            'Cannot know if it is necessary to paginate a not yet executed Pager query'
+        );
+    }
+
+    public function setPage($page)
+    {
+        $this->_setPage($page);
+        $this->_setExecuted(false);
+    }
+
+    private function _setPage($page)
+    {
+        $page = intval($page);
+        $this->_page = ($page <= 0) ? 1 : $page;
+    }
+
+    public function getMaxPerPage()
+    {
+        return $this->_maxPerPage;
+    }
+
+    public function setMaxPerPage($max)
+    {
+        if ($max > 0) {
+            $this->_maxPerPage = $max;
+        } else if ($max == 0) {
+            $this->_maxPerPage = 25;
+        } else {
+            $this->_maxPerPage = abs($max);
+        }
+
+        $this->_setExecuted(false);
+    }
+
+    public function getResultsInPage()
+    {
+        $page = $this->getPage();
+
+        if ($page != $this->getLastPage()) {
+            return $this->getMaxPerPage();
+        }
+
+        $offset = ($this->getPage() - 1) * $this->getMaxPerPage();
+
+        return abs($this->getNumResults() - $offset);
+    }
+
+    public function getQuery()
+    {
+        return $this->_query;
+    }
+
+    protected function _setQuery($query)
+    {
+        if (is_string($query)) {
+            $query = IPF_ORM_Query::create()->parseQuery($query);
+        }
+
+        $this->_query = $query;
+    }
+
+    public function getCountQuery()
+    {
+        return ($this->_countQuery !== null) ? $this->_countQuery : $this->_query;
+    }
+
+    public function setCountQuery($query, $params = null)
+    {
+        if (is_string($query)) {
+            $query = IPF_ORM_Query::create()->parseQuery($query);
+        }
+
+        $this->_countQuery = $query;
+
+        $this->setCountQueryParams($params);
+
+        $this->_setExecuted(false);
+    }
+
+    public function getCountQueryParams($defaultParams = array())
+    {
+        return ($this->_countQueryParams !== null) ? $this->_countQueryParams : $defaultParams;
+    }
+
+    public function setCountQueryParams($params = array(), $append = false)
+    {
+        if ($append && is_array($this->_countQueryParams)) {
+            $this->_countQueryParams = array_merge($this->_countQueryParams, $params);
+        } else {
+            if ($params !== null && !is_array($params)) {
+                $params = array($params);
+            }
+
+            $this->_countQueryParams = $params;
+        }
+
+        $this->_setExecuted(false);
+    }
+
+    public function execute($params = array(), $hydrationMode = IPF_ORM::FETCH_RECORD)
+    {
+        if (!$this->getExecuted()) {
+            $this->_initialize($params);
+        }
+        return $this->getQuery()->execute($params, $hydrationMode);
+    }
+
+    public function __debugInfo()
+    {
+        return array(
+            'page' => $this->getPage(),
+            'size' => $this->getMaxPerPage(),
+            'query' => $this->getQuery(),
+        );
+    }
+}
+
diff --git a/src/orm/pager/layout.php b/src/orm/pager/layout.php
new file mode 100644 (file)
index 0000000..a5c1a32
--- /dev/null
@@ -0,0 +1,236 @@
+<?php
+
+class IPF_ORM_Pager_Layout
+{
+    private $_pager;
+    private $_pagerRange;
+    private $_template;
+    private $_selectedTemplate;
+    private $_separatorTemplate;
+    private $_urlMask;
+    private $_maskReplacements = array();
+
+    public function __construct($pager, $pagerRange, $urlMask)
+    {
+        $this->_setPager($pager);
+        $this->_setPagerRange($pagerRange);
+        $this->_setUrlMask($urlMask);
+
+        $this->setTemplate('[<a href="{%url}">{%page}</a>]');
+        $this->setSelectedTemplate('');
+        $this->setSeparatorTemplate('');
+    }
+
+    public function getPager()
+    {
+        return $this->_pager;
+    }
+
+    protected function _setPager($pager)
+    {
+        $this->_pager = $pager;
+    }
+
+    public function execute($params = array(), $hydrationMode = IPF_ORM::FETCH_RECORD)
+    {
+        return $this->getPager()->execute($params, $hydrationMode);
+    }
+
+    public function getPagerRange()
+    {
+        return $this->_pagerRange;
+    }
+
+    protected function _setPagerRange($pagerRange)
+    {
+        $this->_pagerRange = $pagerRange;
+        $this->getPagerRange()->setPager($this->getPager());
+    }
+
+    public function getUrlMask()
+    {
+        return $this->_urlMask;
+    }
+
+    protected function _setUrlMask($urlMask)
+    {
+        $this->_urlMask = $urlMask;
+    }
+
+    public function getTemplate()
+    {
+        return $this->_template;
+    }
+
+    public function setTemplate($template)
+    {
+        $this->_template = $template;
+    }
+
+    public function getSelectedTemplate()
+    {
+        return $this->_selectedTemplate;
+    }
+
+    public function setSelectedTemplate($selectedTemplate)
+    {
+        $this->_selectedTemplate = $selectedTemplate;
+    }
+
+    public function getSeparatorTemplate()
+    {
+        return $this->_separatorTemplate;
+    }
+
+    public function setSeparatorTemplate($separatorTemplate)
+    {
+        $this->_separatorTemplate = $separatorTemplate;
+    }
+
+    public function addMaskReplacement($oldMask, $newMask, $asValue = false)
+    {
+        if (($oldMask = trim($oldMask)) != 'page_number') {
+            $this->_maskReplacements[$oldMask] = array(
+                'newMask' => $newMask,
+                'asValue' => ($asValue === false) ? false : true
+            );
+        }
+    }
+
+    public function removeMaskReplacement($oldMask)
+    {
+        if (isset($this->_maskReplacements[$oldMask])) {
+            $this->_maskReplacements[$oldMask] = null;
+            unset($this->_maskReplacements[$oldMask]);
+        }
+    }
+    
+    public function cleanMaskReplacements()
+    {
+        $this->_maskReplacements = null;
+        $this->_maskReplacements = array();
+    }
+
+    public function display($options = array(), $return = true)
+    {
+        $range = $this->getPagerRange()->rangeAroundPage();
+        $str = '';
+
+        // For each page in range
+        for ($i = 0, $l = count($range); $i < $l; $i++) {
+            // Define some optional mask values
+            $options['page_number'] = $range[$i];
+
+            $str .= $this->processPage($options);
+
+            // Apply separator between pages
+            if ($i < $l - 1) {
+                $str .= $this->getSeparatorTemplate();
+            }
+        }
+
+        // Possible wish to return value instead of print it on screen
+        if ($return) {
+            return $str;
+        }
+
+        echo $str;
+    }
+
+    public function processPage($options = array())
+    {
+        // Check if at least basic options are defined
+        if (!isset($options['page_number'])) {
+            throw new IPF_ORM_Exception(
+                'Cannot process template of the given page. ' .
+                'Missing at least one of needed parameters: \'page\' or \'page_number\''
+            );
+
+            // Should never reach here
+            return '';
+        }
+
+        // Assign "page" options index if not defined yet
+        if (!isset($this->_maskReplacements['page']) && !isset($options['page'])) {
+            $options['page'] = $options['page_number'];
+        }
+
+        return $this->_parseTemplate($options);
+    }
+
+    public function __toString()
+    {
+      return $this->display(array(), true);
+    }
+
+    protected function _parseTemplate($options = array())
+    {
+        $str = $this->_parseUrlTemplate($options);
+        $replacements = $this->_parseReplacementsTemplate($options);
+
+        return strtr($str, $replacements);
+    }
+
+    protected function _parseUrlTemplate($options = array())
+    {
+        $str = '';
+
+        // If given page is the current active one
+        if ($options['page_number'] == $this->getPager()->getPage()) {
+            $str = $this->_parseMaskReplacements($this->getSelectedTemplate());
+        }
+
+        // Possible attempt where Selected == Template
+        if ($str == '') {
+            $str = $this->_parseMaskReplacements($this->getTemplate());
+        }
+
+        return $str;
+    }
+
+    protected function _parseReplacementsTemplate($options = array())
+    {
+        // Defining "url" options index to allow {%url} mask
+        $options['url'] = $this->_parseUrl($options);
+
+        $replacements = array();
+
+        foreach ($options as $k => $v) {
+            $replacements['{%'.$k.'}'] = $v;
+        }
+
+        return $replacements;
+    }
+
+    protected function _parseUrl($options = array())
+    {
+        $str = $this->_parseMaskReplacements($this->getUrlMask());
+
+        $replacements = array();
+
+        foreach ($options as $k => $v) {
+            $replacements['{%'.$k.'}'] = $v;
+        }
+
+        return strtr($str, $replacements);
+    }
+
+    protected function _parseMaskReplacements($str)
+    {
+        $replacements = array();
+
+        foreach ($this->_maskReplacements as $k => $v) {
+            $replacements['{%'.$k.'}'] = ($v['asValue'] === true) ? $v['newMask'] : '{%'.$v['newMask'].'}';
+        }
+
+        return strtr($str, $replacements);
+    }
+
+    public function __debugInfo()
+    {
+        return array(
+            'pager' => $this->getPager()
+        );
+    }
+}
+
diff --git a/src/orm/pager/layoutarrows.php b/src/orm/pager/layoutarrows.php
new file mode 100644 (file)
index 0000000..57c54d0
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+class IPF_ORM_Pager_LayoutArrows extends IPF_ORM_Pager_Layout
+{
+    public function display($options = array(), $return = true)
+    {
+        $pager = $this->getPager();
+        $str = '';
+
+        $range = $this->getPagerRange()->rangeAroundPage();
+
+        if ($pager->getFirstPage() != $pager->getLastPage())
+        {
+            $this->removeMaskReplacement('page');
+
+            if ($range[0] > 1)
+            {
+                $options['page_number'] = 1;
+                $str .= $this->processPage($options);
+
+                if ($range[0] > 2)
+                {
+                    $options['page_number'] = 2;
+                    $str .= $this->processPage($options);
+                }
+
+                if ($range[0]>3)
+                    $str .= ' ... ';
+            }
+
+            // Pages listing
+            $str .= parent::display($options, true);
+
+            $range = $this->getPagerRange()->rangeAroundPage();
+            $last_range = $range[count($range)-1];
+
+            if ($last_range < $pager->getLastPage())
+            {
+                if ($last_range < $pager->getLastPage() - 2)
+                {
+                    $str .= ' ... ';
+                }
+
+                if ($last_range < $pager->getLastPage() - 1)
+                {
+                    $options['page_number'] = $pager->getLastPage() - 1;
+                    $str .= $this->processPage($options);
+                }
+
+                if ($last_range < $pager->getLastPage())
+                {
+                    $options['page_number'] = $pager->getLastPage();
+                    $str .= $this->processPage($options);
+                }
+            }
+        }
+
+        if ($return)
+            return $str;
+
+        echo $str;
+    }
+}
+
diff --git a/src/orm/pager/range.php b/src/orm/pager/range.php
new file mode 100644 (file)
index 0000000..18ffd8f
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+abstract class IPF_ORM_Pager_Range
+{
+    protected $_options;
+    private $pager;
+
+    final public function __construct($options = array(), $pager = null)
+    {
+        $this->_setOptions($options);
+
+        if ($pager !== null) {
+            $this->setPager($pager);
+        }
+    }
+
+    public function getPager()
+    {
+        return $this->pager;
+    }
+
+    public function setPager($pager)
+    {
+        $this->pager = $pager;
+
+        // Lazy-load initialization. It only should be called when all
+        // needed information data is ready (this can only happens when we have
+        // options stored and a IPF_ORM_Pager assocated)
+        $this->_initialize();
+    }
+
+    public function getOptions()
+    {
+        return $this->_options;
+    }
+
+    public function getOption($option)
+    {
+        if (isset($this->_options[$option])) {
+            return $this->_options[$option];
+        }
+
+        throw new IPF_ORM_Exception(
+            'Cannot access unexistent option \'' . $option . '\' in IPF_ORM_Pager_Range class'
+        );
+    }
+
+    protected function _setOptions($options)
+    {
+        $this->_options = $options;
+    }
+
+    public function isInRange($page)
+    {
+        return (array_search($page, $this->rangeAroundPage()) !== false);
+    }
+
+    abstract protected function _initialize();
+
+    abstract public function rangeAroundPage();
+}
diff --git a/src/orm/pager/range/jumping.php b/src/orm/pager/range/jumping.php
new file mode 100644 (file)
index 0000000..4324921
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+class IPF_ORM_Pager_Range_Jumping extends IPF_ORM_Pager_Range
+{
+    private $_chunkLength;
+
+    protected function _initialize()
+    {
+        if (isset($this->_options['chunk'])) {
+            $this->_setChunkLength($this->_options['chunk']);
+        } else {
+            throw new IPF_ORM_Exception('Missing parameter \'chunk\' that must be define in options.');
+        }
+    }
+
+    public function getChunkLength()
+    {
+        return $this->_chunkLength;
+    }
+
+    protected function _setChunkLength($chunkLength)
+    {
+        $this->_chunkLength = $chunkLength;
+    }
+
+    public function rangeAroundPage()
+    {
+        $pager = $this->getPager();
+
+        if ($pager->getExecuted()) {
+            $page = $pager->getPage();
+
+            // Define initial assignments for StartPage and EndPage
+            $startPage = $page - ($page - 1) % $this->getChunkLength();
+            $endPage = ($startPage + $this->getChunkLength()) - 1;
+
+            // Check for EndPage out-range
+            if ($endPage > $pager->getLastPage()) {
+                $endPage = $pager->getLastPage();
+            }
+
+            // No need to check for out-range in start, it will never happens
+
+            return range($startPage, $endPage);
+        }
+
+        throw new IPF_ORM_Exception(
+            'Cannot retrieve the range around the page of a not yet executed Pager query'
+        );
+    }
+}
diff --git a/src/orm/pager/range/sliding.php b/src/orm/pager/range/sliding.php
new file mode 100644 (file)
index 0000000..cf8198e
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+
+class IPF_ORM_Pager_Range_Sliding extends IPF_ORM_Pager_Range
+{
+    private $_chunkLength;
+
+    protected function _initialize()
+    {
+        if (isset($this->_options['chunk'])) {
+            $this->_setChunkLength($this->_options['chunk']);
+        } else {
+            throw new IPF_ORM_Exception('Missing parameter \'chunk\' that must be defined in options.');
+        }
+    }
+
+    public function getChunkLength()
+    {
+        return $this->_chunkLength;
+    }
+
+    protected function _setChunkLength($chunkLength)
+    {
+        $chunkLength = (int) $chunkLength;
+        if (!$chunkLength) {
+            $chunkLength = 1;
+        } else {
+            $this->_chunkLength = $chunkLength;
+        }
+    }
+
+    public function rangeAroundPage()
+    {
+        $pager = $this->getPager();
+
+        if ($pager->getExecuted()) {
+            $page  = $pager->getPage();
+            $pages = $pager->getLastPage();
+
+            $chunk = $this->getChunkLength();
+
+            if ($chunk > $pages) {
+                $chunk = $pages;
+            }
+
+            $chunkStart = $page - (floor($chunk / 2));
+            $chunkEnd   = $page + (ceil($chunk / 2)-1);
+
+            if ($chunkStart < 1) {
+                $adjust = 1 - $chunkStart;
+                $chunkStart = 1;
+                $chunkEnd = $chunkEnd + $adjust;
+            }
+
+            if ($chunkEnd > $pages) {
+                $adjust = $chunkEnd - $pages;
+                $chunkStart = $chunkStart - $adjust;
+                $chunkEnd = $pages;
+            }
+            return range($chunkStart, $chunkEnd);
+        }
+
+        throw new IPF_ORM_Exception(
+            'Cannot retrieve the range around the page of a not yet executed Pager query'
+        );
+    }
+}
diff --git a/src/orm/query.php b/src/orm/query.php
new file mode 100644 (file)
index 0000000..1830c33
--- /dev/null
@@ -0,0 +1,1259 @@
+<?php
+
+class IPF_ORM_Query extends IPF_ORM_Query_Abstract implements Countable, Serializable
+{
+    protected static $_keywords  = array('ALL',
+                                         'AND',
+                                         'ANY',
+                                         'AS',
+                                         'ASC',
+                                         'AVG',
+                                         'BETWEEN',
+                                         'BIT_LENGTH',
+                                         'BY',
+                                         'CHARACTER_LENGTH',
+                                         'CHAR_LENGTH',
+                                         'CURRENT_DATE',
+                                         'CURRENT_TIME',
+                                         'CURRENT_TIMESTAMP',
+                                         'DELETE',
+                                         'DESC',
+                                         'DISTINCT',
+                                         'EMPTY',
+                                         'EXISTS',
+                                         'FALSE',
+                                         'FETCH',
+                                         'FROM',
+                                         'GROUP',
+                                         'HAVING',
+                                         'IN',
+                                         'INDEXBY',
+                                         'INNER',
+                                         'IS',
+                                         'JOIN',
+                                         'LEFT',
+                                         'LIKE',
+                                         'LOWER',
+                                         'MEMBER',
+                                         'MOD',
+                                         'NEW',
+                                         'NOT',
+                                         'NULL',
+                                         'OBJECT',
+                                         'OF',
+                                         'OR',
+                                         'ORDER',
+                                         'OUTER',
+                                         'POSITION',
+                                         'SELECT',
+                                         'SOME',
+                                         'TRIM',
+                                         'TRUE',
+                                         'UNKNOWN',
+                                         'UPDATE',
+                                         'WHERE');
+
+    protected $_subqueryAliases = array();
+    protected $_aggregateAliasMap      = array();
+    protected $_pendingAggregates = array();
+    protected $_isSubquery;
+    protected $_neededTables = array();
+    protected $_pendingSubqueries = array();
+    protected $_pendingFields = array();
+    protected $_parsers = array();
+    protected $_pendingJoinConditions = array();
+    protected $_expressionMap = array();
+    protected $_sql;
+
+    public static function create($conn = null)
+    {
+        return new IPF_ORM_Query($conn);
+    }
+
+    public function reset()
+    {
+        $this->_pendingJoinConditions = array();
+        $this->_pendingSubqueries = array();
+        $this->_pendingFields = array();
+        $this->_neededTables = array();
+        $this->_expressionMap = array();
+        $this->_subqueryAliases = array();
+    }
+
+    public function createSubquery()
+    {
+        $class = get_class($this);
+        $obj   = new $class();
+
+        // copy the aliases to the subquery
+        $obj->copyAliases($this);
+
+        // this prevents the 'id' being selected, re ticket #307
+        $obj->isSubquery(true);
+
+        return $obj;
+    }
+
+    protected function _addPendingJoinCondition($componentAlias, $joinCondition)
+    {
+        $this->_pendingJoins[$componentAlias] = $joinCondition;
+    }
+
+    public function addEnumParam($key, $table = null, $column = null)
+    {
+        $array = (isset($table) || isset($column)) ? array($table, $column) : array();
+
+        if ($key === '?') {
+            $this->_enumParams[] = $array;
+        } else {
+            $this->_enumParams[$key] = $array;
+        }
+    }
+
+    public function getEnumParams()
+    {
+        return $this->_enumParams;
+    }
+
+    public function getDql()
+    {
+        $q = '';
+        $q .= ( ! empty($this->_dqlParts['select']))?  'SELECT '    . implode(', ', $this->_dqlParts['select']) : '';
+        $q .= ( ! empty($this->_dqlParts['from']))?    ' FROM '     . implode(' ', $this->_dqlParts['from']) : '';
+        $q .= ( ! empty($this->_dqlParts['where']))?   ' WHERE '    . implode(' AND ', $this->_dqlParts['where']) : '';
+        $q .= ( ! empty($this->_dqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_dqlParts['groupby']) : '';
+        $q .= ( ! empty($this->_dqlParts['having']))?  ' HAVING '   . implode(' AND ', $this->_dqlParts['having']) : '';
+        $q .= ( ! empty($this->_dqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_dqlParts['orderby']) : '';
+        $q .= ( ! empty($this->_dqlParts['limit']))?   ' LIMIT '    . implode(' ', $this->_dqlParts['limit']) : '';
+        $q .= ( ! empty($this->_dqlParts['offset']))?  ' OFFSET '   . implode(' ', $this->_dqlParts['offset']) : '';
+
+        return $q;
+    }
+
+
+    public function fetchArray($params = array()) {
+        return $this->execute($params, IPF_ORM::HYDRATE_ARRAY);
+    }
+
+    public function fetchOne($params = array(), $hydrationMode = null)
+    {
+        $collection = $this->execute($params, $hydrationMode);
+
+        if (count($collection) === 0) {
+            return false;
+        }
+
+        if ($collection instanceof IPF_ORM_Collection) {
+            return $collection->getFirst();
+        } else if (is_array($collection)) {
+            return array_shift($collection);
+        }
+
+        return false;
+    }
+
+    public function isSubquery($bool = null)
+    {
+        if ($bool === null) {
+            return $this->_isSubquery;
+        }
+
+        $this->_isSubquery = (bool) $bool;
+        return $this;
+    }
+
+    public function getAggregateAlias($dqlAlias)
+    {
+        return $this->getSqlAggregateAlias($dqlAlias);
+    }
+
+    public function getSqlAggregateAlias($dqlAlias)
+    {
+        if (isset($this->_aggregateAliasMap[$dqlAlias])) {
+            // mark the expression as used
+            $this->_expressionMap[$dqlAlias][1] = true;
+
+            return $this->_aggregateAliasMap[$dqlAlias];
+        } else if ( ! empty($this->_pendingAggregates)) {
+            $this->processPendingAggregates();
+
+            return $this->getSqlAggregateAlias($dqlAlias);
+        } else {
+            throw new IPF_ORM_Exception('Unknown aggregate alias: ' . $dqlAlias);
+        }
+    }
+
+    public function getDqlPart($queryPart)
+    {
+        if ( ! isset($this->_dqlParts[$queryPart])) {
+           throw new IPF_ORM_Exception('Unknown query part ' . $queryPart);
+        }
+
+        return $this->_dqlParts[$queryPart];
+    }
+
+    public function contains($dql)
+    {
+      return stripos($this->getDql(), $dql) === false ? false : true;
+    }
+
+    public function processPendingFields($componentAlias)
+    {
+        $tableAlias = $this->getTableAlias($componentAlias);
+        $table      = $this->_queryComponents[$componentAlias]['table'];
+
+        if ( ! isset($this->_pendingFields[$componentAlias])) {
+            if ($this->_hydrator->getHydrationMode() != IPF_ORM::HYDRATE_NONE) {
+                if ( ! $this->_isSubquery && $componentAlias == $this->getRootAlias()) {
+                    throw new IPF_ORM_Exception("The root class of the query (alias $componentAlias) "
+                            . " must have at least one field selected.");
+                }
+            }
+            return;
+        }
+
+        // At this point we know the component is FETCHED (either it's the base class of
+        // the query (FROM xyz) or its a "fetch join").
+
+        // Check that the parent join (if there is one), is a "fetch join", too.
+        if (isset($this->_queryComponents[$componentAlias]['parent'])) {
+            $parentAlias = $this->_queryComponents[$componentAlias]['parent'];
+            if (is_string($parentAlias) && ! isset($this->_pendingFields[$parentAlias])
+                    && $this->_hydrator->getHydrationMode() != IPF_ORM::HYDRATE_NONE) {
+                throw new IPF_ORM_Exception("The left side of the join between "
+                        . "the aliases '$parentAlias' and '$componentAlias' must have at least"
+                        . " the primary key field(s) selected.");
+            }
+        }
+
+        $fields = $this->_pendingFields[$componentAlias];
+
+        // check for wildcards
+        if (in_array('*', $fields)) {
+            $fields = $table->getFieldNames();
+        } else {
+            // only auto-add the primary key fields if this query object is not
+            // a subquery of another query object
+            if ( ! $this->_isSubquery || $this->_hydrator->getHydrationMode() === IPF_ORM::HYDRATE_NONE) {
+                $fields = array_unique(array_merge((array) $table->getIdentifier(), $fields));
+            }
+        }
+
+        $sql = array();
+        foreach ($fields as $fieldName) {
+            $columnName = $table->getColumnName($fieldName);
+            $sql[] = $this->_conn->quoteIdentifier($tableAlias . '.' . $columnName)
+                   . ' AS '
+                   . $this->_conn->quoteIdentifier($tableAlias . '__' . $columnName);
+        }
+
+        $this->_neededTables[] = $tableAlias;
+
+        return implode(', ', $sql);
+    }
+
+    public function parseSelectField($field)
+    {
+        $terms = explode('.', $field);
+
+        if (isset($terms[1])) {
+            $componentAlias = $terms[0];
+            $field = $terms[1];
+        } else {
+            reset($this->_queryComponents);
+            $componentAlias = key($this->_queryComponents);
+            $fields = $terms[0];
+        }
+
+        $tableAlias = $this->getTableAlias($componentAlias);
+        $table      = $this->_queryComponents[$componentAlias]['table'];
+
+
+        // check for wildcards
+        if ($field === '*') {
+            $sql = array();
+
+            foreach ($table->getColumnNames() as $field) {
+                $sql[] = $this->parseSelectField($componentAlias . '.' . $field);
+            }
+
+            return implode(', ', $sql);
+        } else {
+            $name = $table->getColumnName($field);
+
+            $this->_neededTables[] = $tableAlias;
+
+            return $this->_conn->quoteIdentifier($tableAlias . '.' . $name)
+                   . ' AS '
+                   . $this->_conn->quoteIdentifier($tableAlias . '__' . $name);
+        }
+    }
+
+    public function getExpressionOwner($expr)
+    {
+        if (strtoupper(substr(trim($expr, '( '), 0, 6)) !== 'SELECT') {
+            preg_match_all("/[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*[\.[a-z0-9]+]*/i", $expr, $matches);
+
+            $match = current($matches);
+
+            if (isset($match[0])) {
+                $terms = explode('.', $match[0]);
+
+                return $terms[0];
+            }
+        }
+        return $this->getRootAlias();
+
+    }
+
+    public function parseSelect($dql)
+    {
+        $refs = $this->_tokenizer->sqlExplode($dql, ',');
+
+        $pos   = strpos(trim($refs[0]), ' ');
+        $first = substr($refs[0], 0, $pos);
+
+        // check for DISTINCT keyword
+        if ($first === 'DISTINCT') {
+            $this->_sqlParts['distinct'] = true;
+
+            $refs[0] = substr($refs[0], ++$pos);
+        }
+
+        $parsedComponents = array();
+
+        foreach ($refs as $reference) {
+            $reference = trim($reference);
+
+            if (empty($reference)) {
+                continue;
+            }
+
+            $terms = $this->_tokenizer->sqlExplode($reference, ' ');
+
+            $pos   = strpos($terms[0], '(');
+
+            if (count($terms) > 1 || $pos !== false) {
+                $expression = array_shift($terms);
+                $alias = array_pop($terms);
+
+                if ( ! $alias) {
+                    $alias = substr($expression, 0, $pos);
+                }
+
+                $componentAlias = $this->getExpressionOwner($expression);
+                $expression = $this->parseClause($expression);
+
+                $tableAlias = $this->getTableAlias($componentAlias);
+
+                $index    = count($this->_aggregateAliasMap);
+
+                $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index);
+
+                $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias;
+
+                $this->_aggregateAliasMap[$alias] = $sqlAlias;
+                $this->_expressionMap[$alias][0] = $expression;
+
+                $this->_queryComponents[$componentAlias]['agg'][$index] = $alias;
+
+                $this->_neededTables[] = $tableAlias;
+            } else {
+                $e = explode('.', $terms[0]);
+
+                if (isset($e[1])) {
+                    $componentAlias = $e[0];
+                    $field = $e[1];
+                } else {
+                    reset($this->_queryComponents);
+                    $componentAlias = key($this->_queryComponents);
+                    $field = $e[0];
+                }
+
+                $this->_pendingFields[$componentAlias][] = $field;
+            }
+        }
+    }
+
+    public function parseClause($clause)
+    {
+       $clause = trim($clause);
+
+       if (is_numeric($clause)) {
+           return $clause;
+       }
+
+        $terms = $this->_tokenizer->clauseExplode($clause, array(' ', '+', '-', '*', '/', '<', '>', '=', '>=', '<='));
+
+        $str = '';
+        foreach ($terms as $term) {
+            $pos = strpos($term[0], '(');
+
+            if ($pos !== false) {
+                $name = substr($term[0], 0, $pos);
+                $term[0] = $this->parseFunctionExpression($term[0]);
+            } else {
+                if (substr($term[0], 0, 1) !== "'" && substr($term[0], -1) !== "'") {
+
+                    if (strpos($term[0], '.') !== false) {
+                        if ( ! is_numeric($term[0])) {
+                            $e = explode('.', $term[0]);
+
+                            $field = array_pop($e);
+
+                            if ($this->getType() === IPF_ORM_Query::SELECT) {
+                                $componentAlias = implode('.', $e);
+
+                                if (empty($componentAlias)) {
+                                    $componentAlias = $this->getRootAlias();
+                                }
+
+                                $this->load($componentAlias);
+
+                                // check the existence of the component alias
+                                if ( ! isset($this->_queryComponents[$componentAlias])) {
+                                    throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias);
+                                }
+
+                                $table = $this->_queryComponents[$componentAlias]['table'];
+
+                                $def = $table->getDefinitionOf($field);
+
+                                // get the actual field name from alias
+                                $field = $table->getColumnName($field);
+
+                                // check column existence
+                                if ( ! $def) {
+                                    throw new IPF_ORM_Exception('Unknown column ' . $field);
+                                }
+
+                                if (isset($def['owner'])) {
+                                    $componentAlias = $componentAlias . '.' . $def['owner'];
+                                }
+
+                                $tableAlias = $this->getTableAlias($componentAlias);
+
+                                // build sql expression
+                                $term[0] = $this->_conn->quoteIdentifier($tableAlias)
+                                         . '.'
+                                         . $this->_conn->quoteIdentifier($field);
+                            } else {
+                                // build sql expression
+                                $field = $this->getRoot()->getColumnName($field);
+                                $term[0] = $this->_conn->quoteIdentifier($field);
+                            }
+                        }
+                    } else {
+                        if ( ! empty($term[0]) &&
+                             ! in_array(strtoupper($term[0]), self::$_keywords) &&
+                             ! is_numeric($term[0])) {
+
+                            $componentAlias = $this->getRootAlias();
+
+                            $found = false;
+
+                            if ($componentAlias !== false &&
+                                $componentAlias !== null) {
+                                $table = $this->_queryComponents[$componentAlias]['table'];
+
+                                // check column existence
+                                if ($table->hasField($term[0])) {
+                                    $found = true;
+
+                                    $def = $table->getDefinitionOf($term[0]);
+
+                                    // get the actual column name from field name
+                                    $term[0] = $table->getColumnName($term[0]);
+
+
+                                    if (isset($def['owner'])) {
+                                        $componentAlias = $componentAlias . '.' . $def['owner'];
+                                    }
+
+                                    $tableAlias = $this->getTableAlias($componentAlias);
+
+                                    if ($this->getType() === IPF_ORM_Query::SELECT) {
+                                        // build sql expression
+                                        $term[0] = $this->_conn->quoteIdentifier($tableAlias)
+                                                 . '.'
+                                                 . $this->_conn->quoteIdentifier($term[0]);
+                                    } else {
+                                        // build sql expression
+                                        $term[0] = $this->_conn->quoteIdentifier($term[0]);
+                                    }
+                                } else {
+                                    $found = false;
+                                }
+                            }
+
+                            if ( ! $found) {
+                                $term[0] = $this->getSqlAggregateAlias($term[0]);
+                            }
+                        }
+                    }
+                }
+            }
+
+            $str .= $term[0] . $term[1];
+        }
+        return $str;
+    }
+
+    public function parseIdentifierReference($expr)
+    {
+    }
+
+    public function parseFunctionExpression($expr)
+    {
+        $pos = strpos($expr, '(');
+
+        $name = substr($expr, 0, $pos);
+
+        if ($name === '') {
+            return $this->parseSubquery($expr);
+        }
+
+        $argStr = substr($expr, ($pos + 1), -1);
+
+        $args   = array();
+        // parse args
+
+        foreach ($this->_tokenizer->sqlExplode($argStr, ',') as $arg) {
+           $args[] = $this->parseClause($arg);
+        }
+
+        // convert DQL function to its RDBMS specific equivalent
+        try {
+            $expr = call_user_func_array(array($this->_conn->expression, $name), $args);
+        } catch (IPF_ORM_Exception $e) {
+            throw new IPF_ORM_Exception('Unknown function ' . $name . '.');
+        }
+
+        return $expr;
+    }
+
+    public function parseSubquery($subquery)
+    {
+        $trimmed = trim($this->_tokenizer->bracketTrim($subquery));
+
+        // check for possible subqueries
+        if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') {
+            // parse subquery
+            $trimmed = $this->createSubquery()->parseDqlQuery($trimmed)->getQuery();
+        } else {
+            // parse normal clause
+            $trimmed = $this->parseClause($trimmed);
+        }
+
+        return '(' . $trimmed . ')';
+    }
+
+    public function processPendingSubqueries()
+    {
+        foreach ($this->_pendingSubqueries as $value) {
+            list($dql, $alias) = $value;
+
+            $subquery = $this->createSubquery();
+
+            $sql = $subquery->parseDqlQuery($dql, false)->getQuery();
+
+            reset($this->_queryComponents);
+            $componentAlias = key($this->_queryComponents);
+            $tableAlias = $this->getTableAlias($componentAlias);
+
+            $sqlAlias = $tableAlias . '__' . count($this->_aggregateAliasMap);
+
+            $this->_sqlParts['select'][] = '(' . $sql . ') AS ' . $this->_conn->quoteIdentifier($sqlAlias);
+
+            $this->_aggregateAliasMap[$alias] = $sqlAlias;
+            $this->_queryComponents[$componentAlias]['agg'][] = $alias;
+        }
+        $this->_pendingSubqueries = array();
+    }
+
+    public function processPendingAggregates()
+    {
+        // iterate trhough all aggregates
+        foreach ($this->_pendingAggregates as $aggregate) {
+            list ($expression, $components, $alias) = $aggregate;
+
+            $tableAliases = array();
+
+            // iterate through the component references within the aggregate function
+            if ( ! empty ($components)) {
+                foreach ($components as $component) {
+
+                    if (is_numeric($component)) {
+                        continue;
+                    }
+
+                    $e = explode('.', $component);
+
+                    $field = array_pop($e);
+                    $componentAlias = implode('.', $e);
+
+                    // check the existence of the component alias
+                    if ( ! isset($this->_queryComponents[$componentAlias])) {
+                        throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias);
+                    }
+
+                    $table = $this->_queryComponents[$componentAlias]['table'];
+
+                    $field = $table->getColumnName($field);
+
+                    // check column existence
+                    if ( ! $table->hasColumn($field)) {
+                        throw new IPF_ORM_Exception('Unknown column ' . $field);
+                    }
+
+                    $sqlTableAlias = $this->getSqlTableAlias($componentAlias);
+
+                    $tableAliases[$sqlTableAlias] = true;
+
+                    // build sql expression
+
+                    $identifier = $this->_conn->quoteIdentifier($sqlTableAlias . '.' . $field);
+                    $expression = str_replace($component, $identifier, $expression);
+                }
+            }
+
+            if (count($tableAliases) !== 1) {
+                $componentAlias = reset($this->_tableAliasMap);
+                $tableAlias = key($this->_tableAliasMap);
+            }
+
+            $index    = count($this->_aggregateAliasMap);
+            $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index);
+
+            $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias;
+
+            $this->_aggregateAliasMap[$alias] = $sqlAlias;
+            $this->_expressionMap[$alias][0] = $expression;
+
+            $this->_queryComponents[$componentAlias]['agg'][$index] = $alias;
+
+            $this->_neededTables[] = $tableAlias;
+        }
+        // reset the state
+        $this->_pendingAggregates = array();
+    }
+
+    protected function _buildSqlQueryBase()
+    {
+        switch ($this->_type) {
+            case self::DELETE:
+                $q = 'DELETE FROM ';
+            break;
+            case self::UPDATE:
+                $q = 'UPDATE ';
+            break;
+            case self::SELECT:
+                $distinct = ($this->_sqlParts['distinct']) ? 'DISTINCT ' : '';
+                $q = 'SELECT ' . $distinct . implode(', ', $this->_sqlParts['select']) . ' FROM ';
+            break;
+        }
+        return $q;
+    }
+
+    protected function _buildSqlFromPart()
+    {
+        $q = '';
+        foreach ($this->_sqlParts['from'] as $k => $part) {
+            if ($k === 0) {
+                $q .= $part;
+                continue;
+            }
+
+            // preserve LEFT JOINs only if needed
+            // Check if it's JOIN, if not add a comma separator instead of space
+            if (!preg_match('/\bJOIN\b/i', $part) && !isset($this->_pendingJoinConditions[$k])) {
+                $q .= ', ' . $part;
+            } else {
+                $e = explode(' ', $part);
+
+                if (substr($part, 0, 9) === 'LEFT JOIN') {
+                    $aliases = array_merge($this->_subqueryAliases,
+                                array_keys($this->_neededTables));
+
+                    if ( ! in_array($e[3], $aliases) &&
+                        ! in_array($e[2], $aliases) &&
+
+                        ! empty($this->_pendingFields)) {
+                        continue;
+                    }
+
+                }
+
+                if (isset($this->_pendingJoinConditions[$k])) {
+                    $parser = new IPF_ORM_JoinCondition($this, $this->_tokenizer);
+
+                    if (strpos($part, ' ON ') !== false) {
+                        $part .= ' AND ';
+                    } else {
+                        $part .= ' ON ';
+                    }
+                    $part .= $parser->parse($this->_pendingJoinConditions[$k]);
+
+                    unset($this->_pendingJoinConditions[$k]);
+                }
+
+                $tableAlias = trim($e[3], '"');
+                $componentAlias = $this->getComponentAlias($tableAlias);
+
+                $string = $this->getInheritanceCondition($componentAlias);
+
+                if ($string) {
+                    $q .= ' ' . $part . ' AND ' . $string;
+                } else {
+                    $q .= ' ' . $part;
+                }
+            }
+
+            $this->_sqlParts['from'][$k] = $part;
+        }
+        return $q;
+    }
+
+    public function getSqlQuery($params = array())
+    {
+        if ($this->_state !== self::STATE_DIRTY) {
+           return $this->_sql;
+        }
+
+        // reset the state
+        if ( ! $this->isSubquery()) {
+            $this->_queryComponents = array();
+            $this->_pendingAggregates = array();
+            $this->_aggregateAliasMap = array();
+        }
+        $this->reset();
+
+        // invoke the preQuery hook
+        $this->_preQuery();
+
+        // process the DQL parts => generate the SQL parts.
+        // this will also populate the $_queryComponents.
+        foreach ($this->_dqlParts as $queryPartName => $queryParts) {
+            $this->_processDqlQueryPart($queryPartName, $queryParts);
+        }
+        $this->_state = self::STATE_CLEAN;
+
+        $params = $this->convertEnums($params);
+
+        // Proceed with the generated SQL
+
+        if (empty($this->_sqlParts['from'])) {
+            return false;
+        }
+
+        $map = reset($this->_queryComponents);
+        $table = $map['table'];
+        $rootAlias = key($this->_queryComponents);
+
+        $sql = array();
+        if ( ! empty($this->_pendingFields)) {
+            foreach ($this->_queryComponents as $alias => $map) {
+                $fieldSql = $this->processPendingFields($alias);
+                if ( ! empty($fieldSql)) {
+                    $sql[] = $fieldSql;
+                }
+            }
+        }
+        if ( ! empty($sql)) {
+            array_unshift($this->_sqlParts['select'], implode(', ', $sql));
+        }
+
+        $this->_pendingFields = array();
+
+        // build the basic query
+        $q  = $this->_buildSqlQueryBase();
+        $q .= $this->_buildSqlFromPart();
+
+        if ( ! empty($this->_sqlParts['set'])) {
+            $q .= ' SET ' . implode(', ', $this->_sqlParts['set']);
+        }
+
+        $string = $this->getInheritanceCondition($this->getRootAlias());
+
+        // apply inheritance to WHERE part
+        if ( ! empty($string)) {
+            if (substr($string, 0, 1) === '(' && substr($string, -1) === ')') {
+                $this->_sqlParts['where'][] = $string;
+            } else {
+                $this->_sqlParts['where'][] = '(' . $string . ')';
+            }
+        }
+
+        $q .= ( ! empty($this->_sqlParts['where']))?   ' WHERE '    . implode(' AND ', $this->_sqlParts['where']) : '';
+        $q .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby'])  : '';
+        $q .= ( ! empty($this->_sqlParts['having']))?  ' HAVING '   . implode(' AND ', $this->_sqlParts['having']): '';
+        $q .= ( ! empty($this->_sqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby'])  : '';
+
+        $q = $this->_conn->modifyLimitQuery($q, $this->_sqlParts['limit'], $this->_sqlParts['offset']);
+
+        // return to the previous state
+        if ( ! empty($string)) {
+            array_pop($this->_sqlParts['where']);
+        }
+        $this->_sql = $q;
+
+        return $q;
+    }
+
+    public function parseDqlQuery($query, $clear = true)
+    {
+        if ($clear) {
+            $this->clear();
+        }
+
+        $query = trim($query);
+        $query = str_replace("\r", "\n", str_replace("\r\n", "\n", $query));
+        $query = str_replace("\n", ' ', $query);
+
+        $parts = $this->_tokenizer->tokenizeQuery($query);
+
+        foreach ($parts as $partName => $subParts) {
+            $subParts = trim($subParts);
+            $partName = strtolower($partName);
+            switch ($partName) {
+                case 'create':
+                    $this->_type = self::CREATE;
+                break;
+                case 'insert':
+                    $this->_type = self::INSERT;
+                break;
+                case 'delete':
+                    $this->_type = self::DELETE;
+                break;
+                case 'select':
+                    $this->_type = self::SELECT;
+                    $this->_addDqlQueryPart($partName, $subParts);
+                break;
+                case 'update':
+                    $this->_type = self::UPDATE;
+                    $partName = 'from';
+                case 'from':
+                    $this->_addDqlQueryPart($partName, $subParts);
+                break;
+                case 'set':
+                    $this->_addDqlQueryPart($partName, $subParts, true);
+                break;
+                case 'group':
+                case 'order':
+                    $partName .= 'by';
+                case 'where':
+                case 'having':
+                case 'limit':
+                case 'offset':
+                    $this->_addDqlQueryPart($partName, $subParts);
+                break;
+            }
+        }
+
+        return $this;
+    }
+
+    public function load($path, $loadFields = true)
+    {
+        if (isset($this->_queryComponents[$path])) {
+            return $this->_queryComponents[$path];
+        }
+
+        $e = $this->_tokenizer->quoteExplode($path, ' INDEXBY ');
+
+        $mapWith = null;
+        if (count($e) > 1) {
+            $mapWith = trim($e[1]);
+
+            $path = $e[0];
+        }
+
+        // parse custom join conditions
+        $e = explode(' ON ', $path);
+
+        $joinCondition = '';
+
+        if (count($e) > 1) {
+            $joinCondition = $e[1];
+            $overrideJoin = true;
+            $path = $e[0];
+        } else {
+            $e = explode(' WITH ', $path);
+
+            if (count($e) > 1) {
+                $joinCondition = $e[1];
+                $path = $e[0];
+            }
+            $overrideJoin = false;
+        }
+
+        $tmp            = explode(' ', $path);
+        $componentAlias = $originalAlias = (count($tmp) > 1) ? end($tmp) : null;
+
+        $e = preg_split("/[.:]/", $tmp[0], -1);
+
+        $fullPath = $tmp[0];
+        $prevPath = '';
+        $fullLength = strlen($fullPath);
+
+        if (isset($this->_queryComponents[$e[0]])) {
+            $table = $this->_queryComponents[$e[0]]['table'];
+            $componentAlias = $e[0];
+
+            $prevPath = $parent = array_shift($e);
+        }
+
+        foreach ($e as $key => $name) {
+            // get length of the previous path
+            $length = strlen($prevPath);
+
+            // build the current component path
+            $prevPath = ($prevPath) ? $prevPath . '.' . $name : $name;
+
+            $delimeter = substr($fullPath, $length, 1);
+
+            // if an alias is not given use the current path as an alias identifier
+            if (strlen($prevPath) === $fullLength && isset($originalAlias)) {
+                $componentAlias = $originalAlias;
+            } else {
+                $componentAlias = $prevPath;
+            }
+
+            // if the current alias already exists, skip it
+            if (isset($this->_queryComponents[$componentAlias])) {
+                throw new IPF_ORM_Exception("Duplicate alias '$componentAlias' in query.");
+            }
+
+            if ( ! isset($table)) {
+                // process the root of the path
+
+                $table = $this->loadRoot($name, $componentAlias);
+            } else {
+                $join = ($delimeter == ':') ? 'INNER JOIN ' : 'LEFT JOIN ';
+
+                $relation = $table->getRelation($name);
+                $localTable = $table;
+
+                $table    = $relation->getTable();
+                $this->_queryComponents[$componentAlias] = array('table' => $table,
+                                                                 'parent'   => $parent,
+                                                                 'relation' => $relation,
+                                                                 'map'      => null);
+
+                $localAlias   = $this->getTableAlias($parent, $table->getTableName());
+                $foreignAlias = $this->getTableAlias($componentAlias, $relation->getTable()->getTableName());
+
+                $foreignSql   = $this->_conn->quoteIdentifier($relation->getTable()->getTableName())
+                              . ' '
+                              . $this->_conn->quoteIdentifier($foreignAlias);
+
+                $map = $relation->getTable()->inheritanceMap;
+
+                if ( ! $loadFields || ! empty($map) || $joinCondition) {
+                    $this->_subqueryAliases[] = $foreignAlias;
+                }
+
+                if ($relation instanceof IPF_ORM_Relation_Association) {
+                    $asf = $relation->getAssociationTable();
+
+                    $assocTableName = $asf->getTableName();
+
+                    if ( ! $loadFields || ! empty($map) || $joinCondition) {
+                        $this->_subqueryAliases[] = $assocTableName;
+                    }
+
+                    $assocPath = $prevPath . '.' . $asf->getComponentName();
+
+                    $this->_queryComponents[$assocPath] = array('parent' => $prevPath, 'relation' => $relation, 'table' => $asf);
+
+                    $assocAlias = $this->getTableAlias($assocPath, $asf->getTableName());
+
+                    $queryPart = $join . $assocTableName . ' ' . $assocAlias;
+
+                    $queryPart .= ' ON ' . $localAlias
+                                . '.'
+                                . $localTable->getColumnName($localTable->getIdentifier()) // what about composite keys?
+                                . ' = '
+                                . $assocAlias . '.' . $relation->getLocal();
+
+                    if ($relation->isEqual()) {
+                        // equal nest relation needs additional condition
+                        $queryPart .= ' OR ' . $localAlias
+                                    . '.'
+                                    . $table->getColumnName($table->getIdentifier())
+                                    . ' = '
+                                    . $assocAlias . '.' . $relation->getForeign();
+                    }
+
+                    $this->_sqlParts['from'][] = $queryPart;
+
+                    $queryPart = $join . $foreignSql;
+
+                    if ( ! $overrideJoin) {
+                        $queryPart .= $this->buildAssociativeRelationSql($relation, $assocAlias, $foreignAlias, $localAlias);
+                    }
+                } else {
+                    $queryPart = $this->buildSimpleRelationSql($relation, $foreignAlias, $localAlias, $overrideJoin, $join);
+                }
+
+                $queryPart .= $this->buildInheritanceJoinSql($table->getComponentName(), $componentAlias);
+
+                $this->_sqlParts['from'][$componentAlias] = $queryPart;
+                if ( ! empty($joinCondition)) {
+                    $this->_pendingJoinConditions[$componentAlias] = $joinCondition;
+                }
+            }
+            if ($loadFields) {
+
+                $restoreState = false;
+                // load fields if necessary
+                if ($loadFields && empty($this->_dqlParts['select'])) {
+                    $this->_pendingFields[$componentAlias] = array('*');
+                }
+            }
+            $parent = $prevPath;
+        }
+
+        $table = $this->_queryComponents[$componentAlias]['table'];
+
+        return $this->buildIndexBy($componentAlias, $mapWith);
+    }
+
+    protected function buildSimpleRelationSql(IPF_ORM_Relation $relation, $foreignAlias, $localAlias, $overrideJoin, $join)
+    {
+        $queryPart = $join . $this->_conn->quoteIdentifier($relation->getTable()->getTableName())
+                           . ' '
+                           . $this->_conn->quoteIdentifier($foreignAlias);
+
+        if ( ! $overrideJoin) {
+            $queryPart .= ' ON '
+                       . $this->_conn->quoteIdentifier($localAlias . '.' . $relation->getLocal())
+                       . ' = '
+                       . $this->_conn->quoteIdentifier($foreignAlias . '.' . $relation->getForeign());
+        }
+
+        return $queryPart;
+    }
+
+    protected function buildIndexBy($componentAlias, $mapWith = null)
+    {
+        $table = $this->_queryComponents[$componentAlias]['table'];
+
+        $indexBy = null;
+
+        if (isset($mapWith)) {
+            $terms = explode('.', $mapWith);
+
+            if (isset($terms[1])) {
+                $indexBy = $terms[1];
+            }
+        } elseif ($table->getBoundQueryPart('indexBy') !== null) {
+            $indexBy = $table->getBoundQueryPart('indexBy');
+        }
+
+        if ($indexBy !== null) {
+            if ( ! $table->hasColumn($table->getColumnName($indexBy))) {
+                throw new IPF_ORM_Exception("Couldn't use key mapping. Column " . $indexBy . " does not exist.");
+            }
+
+            $this->_queryComponents[$componentAlias]['map'] = $indexBy;
+        }
+
+        return $this->_queryComponents[$componentAlias];
+    }
+
+
+    protected function buildAssociativeRelationSql(IPF_ORM_Relation $relation, $assocAlias, $foreignAlias, $localAlias)
+    {
+        $table = $relation->getTable();
+
+        $queryPart = ' ON ';
+
+        if ($relation->isEqual()) {
+            $queryPart .= '(';
+        }
+
+        $localIdentifier = $table->getColumnName($table->getIdentifier());
+
+        $queryPart .= $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
+                    . ' = '
+                    . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getForeign());
+
+        if ($relation->isEqual()) {
+            $queryPart .= ' OR '
+                        . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
+                        . ' = '
+                        . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getLocal())
+                        . ') AND '
+                        . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
+                        . ' != '
+                        . $this->_conn->quoteIdentifier($localAlias . '.' . $localIdentifier);
+        }
+
+        return $queryPart;
+    }
+
+    public function loadRoot($name, $componentAlias)
+    {
+        // get the connection for the component
+        $manager = IPF_ORM_Manager::getInstance();
+
+        $table = $this->_conn->getTable($name);
+        $tableName = $table->getTableName();
+
+        // get the short alias for this table
+        $tableAlias = $this->getTableAlias($componentAlias, $tableName);
+        // quote table name
+        $queryPart = $this->_conn->quoteIdentifier($tableName);
+
+        if ($this->_type === self::SELECT) {
+            $queryPart .= ' ' . $this->_conn->quoteIdentifier($tableAlias);
+        }
+
+        $this->_tableAliasMap[$tableAlias] = $componentAlias;
+
+        $queryPart .= $this->buildInheritanceJoinSql($name, $componentAlias);
+
+        $this->_sqlParts['from'][] = $queryPart;
+
+        $this->_queryComponents[$componentAlias] = array('table' => $table, 'map' => null);
+
+        return $table;
+    }
+
+    public function buildInheritanceJoinSql($name, $componentAlias)
+    {
+        // get the connection for the component
+        $manager = IPF_ORM_Manager::getInstance();
+
+        $table = $this->_conn->getTable($name);
+        $tableName = $table->getTableName();
+
+        // get the short alias for this table
+        $tableAlias = $this->getTableAlias($componentAlias, $tableName);
+
+        return '';
+    }
+
+    public function getCountQuery()
+    {
+        // triggers dql parsing/processing
+        $this->getQuery(); // this is ugly
+
+        // initialize temporary variables
+        $where  = $this->_sqlParts['where'];
+        $having = $this->_sqlParts['having'];
+        $groupby = $this->_sqlParts['groupby'];
+        $map = reset($this->_queryComponents);
+        $componentAlias = key($this->_queryComponents);
+        $tableAlias = $this->_conn->quoteIdentifier($this->getTableAlias($componentAlias));
+        $table = $map['table'];
+
+        $idColumnNames = array();
+        foreach ($table->getIdentifierColumnNames() as $column) {
+            $idColumnNames[] = $tableAlias . '.' . $this->_conn->quoteIdentifier($column);
+        }
+
+        // build the query base
+        $q  = 'SELECT COUNT(DISTINCT ' . implode(' || ', $idColumnNames) . ') AS num_results';
+
+        foreach ($this->_sqlParts['select'] as $field) {
+            if (strpos($field, '(') !== false) {
+                $q .= ', ' . $field;
+            }
+        }
+
+        $q .= ' FROM ' . $this->_buildSqlFromPart();
+
+        // append column aggregation inheritance (if needed)
+        $string = $this->getInheritanceCondition($this->getRootAlias());
+
+        if ( ! empty($string)) {
+            $where[] = $string;
+        }
+
+        // append conditions
+        $q .= ( ! empty($where)) ?  ' WHERE '  . implode(' AND ', $where) : '';
+
+        if ( ! empty($groupby)) {
+            // Maintain existing groupby
+            $q .= ' GROUP BY '  . implode(', ', $groupby);
+        } else {
+            // Default groupby to primary identifier. Database defaults to this internally
+            // This is required for situations where the user has aggregate functions in the select part
+            // Without the groupby it fails
+            $q .= ' GROUP BY ' . implode(', ', $idColumnNames);
+        }
+
+        $q .= ( ! empty($having)) ? ' HAVING ' . implode(' AND ', $having): '';
+
+        return $q;
+    }
+
+    public function count($params = array())
+    {
+        $q = $this->getCountQuery();
+
+        if ( ! is_array($params)) {
+            $params = array($params);
+        }
+
+        $params = array_merge($this->_params['join'], $this->_params['where'], $this->_params['having'], $params);
+
+        $params = $this->convertEnums($params);
+
+        $results = $this->getConnection()->fetchAll($q, $params);
+
+        if (count($results) > 1) {
+            $count = count($results);
+        } else {
+            if (isset($results[0])) {
+                $results[0] = array_change_key_case($results[0], CASE_LOWER);
+                $count = $results[0]['num_results'];
+            } else {
+                $count = 0;
+            }
+        }
+
+        return (int) $count;
+    }
+
+    public function query($query, $params = array(), $hydrationMode = null)
+    {
+        $this->parseDqlQuery($query);
+        return $this->execute($params, $hydrationMode);
+    }
+
+    public function copy(IPF_ORM_Query $query = null)
+    {
+        if ( ! $query) {
+            $query = $this;
+        }
+
+        $new = clone $query;
+
+        return $new;
+    }
+
+    public function __clone()
+    {
+        $this->_parsers = array();
+    }
+
+    public function free()
+    {
+        $this->reset();
+        $this->_parsers = array();
+        $this->_dqlParts = array();
+        $this->_enumParams = array();
+    }
+
+    public function serialize()
+    {
+        $vars = get_object_vars($this);
+    }
+
+    public function unserialize($serialized)
+    {
+    }
+}
diff --git a/src/orm/query/abstract.php b/src/orm/query/abstract.php
new file mode 100644 (file)
index 0000000..6e0f48a
--- /dev/null
@@ -0,0 +1,998 @@
+<?php
+
+abstract class IPF_ORM_Query_Abstract
+{
+    const SELECT = 0;
+    const DELETE = 1;
+    const UPDATE = 2;
+    const INSERT = 3;
+    const CREATE = 4;
+
+    const STATE_CLEAN  = 1;
+    const STATE_DIRTY  = 2;
+    const STATE_DIRECT = 3;
+    const STATE_LOCKED = 4;
+
+    protected $_tableAliasMap = array();
+    protected $_view;
+    protected $_state = IPF_ORM_Query::STATE_CLEAN;
+    protected $_params = array('join' => array(),
+                               'where' => array(),
+                               'set' => array(),
+                               'having' => array());
+
+    protected $_resultCache;
+    protected $_expireResultCache = false;
+    protected $_resultCacheTTL;
+
+    protected $_queryCache;
+    protected $_expireQueryCache = false;
+    protected $_queryCacheTTL;
+
+    protected $_conn;
+
+    protected $_sqlParts = array(
+            'select'    => array(),
+            'distinct'  => false,
+            'forUpdate' => false,
+            'from'      => array(),
+            'set'       => array(),
+            'join'      => array(),
+            'where'     => array(),
+            'groupby'   => array(),
+            'having'    => array(),
+            'orderby'   => array(),
+            'limit'     => false,
+            'offset'    => false,
+            );
+
+    protected $_dqlParts = array(
+                            'from'      => array(),
+                            'select'    => array(),
+                            'forUpdate' => false,
+                            'set'       => array(),
+                            'join'      => array(),
+                            'where'     => array(),
+                            'groupby'   => array(),
+                            'having'    => array(),
+                            'orderby'   => array(),
+                            'limit'     => array(),
+                            'offset'    => array(),
+                            );
+
+    protected $_queryComponents = array();
+    protected $_type = self::SELECT;
+    protected $_hydrator;
+    protected $_tokenizer;
+    protected $_parser;
+    protected $_tableAliasSeeds = array();
+    protected $_options    = array(
+                            'fetchMode'      => IPF_ORM::FETCH_RECORD
+                            );
+    protected $_enumParams = array();
+
+    protected $_pendingSetParams = array();
+    protected $_components;
+    protected $_preQueried = false;
+
+    public function __construct(IPF_ORM_Connection $connection = null,
+            IPF_ORM_Hydrator_Abstract $hydrator = null)
+    {
+        if ($connection === null) {
+            $connection = IPF_ORM_Manager::connection();
+        }
+        if ($hydrator === null) {
+            $hydrator = new IPF_ORM_Hydrator();
+        }
+        $this->_conn = $connection;
+        $this->_hydrator = $hydrator;
+        $this->_tokenizer = new IPF_ORM_Query_Tokenizer();
+        $this->_resultCacheTTL = $this->_conn->getAttribute(IPF_ORM::ATTR_RESULT_CACHE_LIFESPAN);
+        $this->_queryCacheTTL = $this->_conn->getAttribute(IPF_ORM::ATTR_QUERY_CACHE_LIFESPAN);
+    }
+
+    public function setOption($name, $value)
+    {
+        if ( ! isset($this->_options[$name])) {
+            throw new IPF_ORM_Exception('Unknown option ' . $name);
+        }
+        $this->_options[$name] = $value;
+    }
+
+    public function hasTableAlias($sqlTableAlias)
+    {
+        return $this->hasSqlTableAlias($sqlTableAlias);
+    }
+
+    public function hasSqlTableAlias($sqlTableAlias)
+    {
+        return (isset($this->_tableAliasMap[$sqlTableAlias]));
+    }
+
+    public function getTableAliases()
+    {
+        return $this->getTableAliasMap();
+    }
+
+    public function getTableAliasMap()
+    {
+        return $this->_tableAliasMap;
+    }
+
+    public function getQueryPart($part)
+    {
+        return $this->getSqlQueryPart($part);
+    }
+
+    public function getSqlQueryPart($part)
+    {
+        if ( ! isset($this->_sqlParts[$part])) {
+            throw new IPF_ORM_Exception('Unknown SQL query part ' . $part);
+        }
+        return $this->_sqlParts[$part];
+    }
+
+    public function setQueryPart($name, $part)
+    {
+        return $this->setSqlQueryPart($name, $part);
+    }
+
+    public function setSqlQueryPart($name, $part)
+    {
+        if ( ! isset($this->_sqlParts[$name])) {
+            throw new IPF_ORM_Exception('Unknown query part ' . $name);
+        }
+
+        if ($name !== 'limit' && $name !== 'offset') {
+            if (is_array($part)) {
+                $this->_sqlParts[$name] = $part;
+            } else {
+                $this->_sqlParts[$name] = array($part);
+            }
+        } else {
+            $this->_sqlParts[$name] = $part;
+        }
+
+        return $this;
+    }
+
+    public function addQueryPart($name, $part)
+    {
+        return $this->addSqlQueryPart($name, $part);
+    }
+
+    public function addSqlQueryPart($name, $part)
+    {
+        if ( ! isset($this->_sqlParts[$name])) {
+            throw new IPF_ORM_Exception('Unknown query part ' . $name);
+        }
+        if (is_array($part)) {
+            $this->_sqlParts[$name] = array_merge($this->_sqlParts[$name], $part);
+        } else {
+            $this->_sqlParts[$name][] = $part;
+        }
+        return $this;
+    }
+
+    public function removeQueryPart($name)
+    {
+        return $this->removeSqlQueryPart($name);
+    }
+
+    public function removeSqlQueryPart($name)
+    {
+        if ( ! isset($this->_sqlParts[$name])) {
+            throw new IPF_ORM_Exception('Unknown query part ' . $name);
+        }
+
+        if ($name == 'limit' || $name == 'offset') {
+            $this->_sqlParts[$name] = false;
+        } else {
+            $this->_sqlParts[$name] = array();
+        }
+
+        return $this;
+    }
+
+    public function removeDqlQueryPart($name)
+    {
+        if ( ! isset($this->_dqlParts[$name])) {
+            throw new IPF_ORM_Exception('Unknown query part ' . $name);
+        }
+
+        if ($name == 'limit' || $name == 'offset') {
+            $this->_dqlParts[$name] = false;
+        } else {
+            $this->_dqlParts[$name] = array();
+        }
+
+        return $this;
+    }
+
+    public function getParams($params = array())
+    {
+        return array_merge($this->_params['join'], $this->_params['set'], $this->_params['where'], $this->_params['having'], $params);
+    }
+
+    public function setParams(array $params = array()) {
+        $this->_params = $params;
+    }
+
+    public function setView(IPF_ORM_View $view)
+    {
+        $this->_view = $view;
+    }
+
+    public function getView()
+    {
+        return $this->_view;
+    }
+
+    public function convertEnums($params)
+    {
+        $table = $this->getRoot();
+
+        // $position tracks the position of the parameter, to ensure we're converting
+        // the right parameter value when simple ? placeholders are used.
+        // This only works because SET is only allowed in update statements and it's
+        // the first place where parameters can occur.. see issue #935
+        $position = 0;
+        foreach ($this->_pendingSetParams as $fieldName => $value) {
+            $e = explode('.', $fieldName);
+            $fieldName = isset($e[1]) ? $e[1]:$e[0];
+            if ($table->getTypeOf($fieldName) == 'enum') {
+                $value = $value === '?' ? $position : $value;
+                $this->addEnumParam($value, $table, $fieldName);
+            }
+            ++$position;
+        }
+        $this->_pendingSetParams = array();
+
+        foreach ($this->_enumParams as $key => $values) {
+            if (isset($params[$key])) {
+                if ( ! empty($values)) {
+                    $params[$key] = $values[0]->enumIndex($values[1], $params[$key]);
+                }
+            }
+        }
+
+        return $params;
+    }
+
+    public function getInheritanceCondition($componentAlias)
+    {
+        $map = $this->_queryComponents[$componentAlias]['table']->inheritanceMap;
+
+        // No inheritance map so lets just return
+        if (empty($map)) {
+          return;
+        }
+
+        $tableAlias = $this->getSqlTableAlias($componentAlias);
+
+        if ($this->_type !== IPF_ORM_Query::SELECT) {
+            $tableAlias = '';
+        } else {
+            $tableAlias .= '.';
+        }
+
+        $field = key($map);
+        $value = current($map);
+        $identifier = $this->_conn->quoteIdentifier($tableAlias . $field);
+
+        return $identifier . ' = ' . $this->_conn->quote($value);;
+    }
+
+    public function getTableAlias($componentAlias, $tableName = null)
+    {
+        return $this->getSqlTableAlias($componentAlias, $tableName);
+    }
+
+    public function getSqlTableAlias($componentAlias, $tableName = null)
+    {
+        $alias = array_search($componentAlias, $this->_tableAliasMap);
+
+        if ($alias !== false) {
+            return $alias;
+        }
+
+        if ($tableName === null) {
+            throw new IPF_ORM_Exception("Couldn't get short alias for " . $componentAlias);
+        }
+
+        return $this->generateTableAlias($componentAlias, $tableName);
+    }
+
+    public function generateNewTableAlias($oldAlias)
+    {
+        return $this->generateNewSqlTableAlias($oldAlias);
+    }
+
+    public function generateNewSqlTableAlias($oldAlias)
+    {
+        if (isset($this->_tableAliasMap[$oldAlias])) {
+            // generate a new alias
+            $name = substr($oldAlias, 0, 1);
+            $i    = ((int) substr($oldAlias, 1));
+
+            if ($i == 0) {
+                $i = 1;
+            }
+
+            $newIndex  = ($this->_tableAliasSeeds[$name] + $i);
+
+            return $name . $newIndex;
+        }
+
+        return $oldAlias;
+    }
+
+    public function getTableAliasSeed($sqlTableAlias)
+    {
+        return $this->getSqlTableAliasSeed($sqlTableAlias);
+    }
+
+    public function getSqlTableAliasSeed($sqlTableAlias)
+    {
+        if ( ! isset($this->_tableAliasSeeds[$sqlTableAlias])) {
+            return 0;
+        }
+        return $this->_tableAliasSeeds[$sqlTableAlias];
+    }
+
+    public function hasAliasDeclaration($componentAlias)
+    {
+        return isset($this->_queryComponents[$componentAlias]);
+    }
+
+    public function getAliasDeclaration($componentAlias)
+    {
+        return $this->getQueryComponent($componentAlias);
+    }
+
+    public function getQueryComponent($componentAlias)
+    {
+        if ( ! isset($this->_queryComponents[$componentAlias])) {
+            throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias);
+        }
+
+        return $this->_queryComponents[$componentAlias];
+    }
+
+    public function copyAliases(IPF_ORM_Query_Abstract $query)
+    {
+        $this->_tableAliasMap = $query->_tableAliasMap;
+        $this->_queryComponents     = $query->_queryComponents;
+        $this->_tableAliasSeeds = $query->_tableAliasSeeds;
+        return $this;
+    }
+
+    public function getRootAlias()
+    {
+        if ( ! $this->_queryComponents) {
+          $this->getSql();
+        }
+        reset($this->_queryComponents);
+
+        return key($this->_queryComponents);
+    }
+
+    public function getRootDeclaration()
+    {
+        $map = reset($this->_queryComponents);
+        return $map;
+    }
+
+    public function getRoot()
+    {
+        $map = reset($this->_queryComponents);
+
+        if ( ! isset($map['table'])) {
+            throw new IPF_ORM_Exception('Root component not initialized.');
+        }
+
+        return $map['table'];
+    }
+
+    public function generateTableAlias($componentAlias, $tableName)
+    {
+        return $this->generateSqlTableAlias($componentAlias, $tableName);
+    }
+
+    public function generateSqlTableAlias($componentAlias, $tableName)
+    {
+        preg_match('/([^_])/', $tableName, $matches);
+        $char = strtolower($matches[0]);
+
+        $alias = $char;
+
+        if ( ! isset($this->_tableAliasSeeds[$alias])) {
+            $this->_tableAliasSeeds[$alias] = 1;
+        }
+
+        while (isset($this->_tableAliasMap[$alias])) {
+            if ( ! isset($this->_tableAliasSeeds[$alias])) {
+                $this->_tableAliasSeeds[$alias] = 1;
+            }
+            $alias = $char . ++$this->_tableAliasSeeds[$alias];
+        }
+
+        $this->_tableAliasMap[$alias] = $componentAlias;
+
+        return $alias;
+    }
+
+    public function getComponentAlias($sqlTableAlias)
+    {
+        if ( ! isset($this->_tableAliasMap[$sqlTableAlias])) {
+            throw new IPF_ORM_Exception('Unknown table alias ' . $sqlTableAlias);
+        }
+        return $this->_tableAliasMap[$sqlTableAlias];
+    }
+
+    protected function _execute($params)
+    {
+        $params = $this->_conn->convertBooleans($params);
+
+        if ( ! $this->_view) {
+            if ($this->_queryCache || $this->_conn->getAttribute(IPF_ORM::ATTR_QUERY_CACHE)) {
+                $queryCacheDriver = $this->getQueryCacheDriver();
+                // calculate hash for dql query
+                $dql = $this->getDql();
+                $hash = md5($dql . 'IPF_ORM_QUERY_CACHE_SALT');
+                $cached = $queryCacheDriver->fetch($hash);
+                if ($cached) {
+                    $query = $this->_constructQueryFromCache($cached);
+                } else {
+                    $query = $this->getSqlQuery($params);
+                    $serializedQuery = $this->getCachedForm($query);
+                    $queryCacheDriver->save($hash, $serializedQuery, $this->getQueryCacheLifeSpan());
+                }
+            } else {
+                $query = $this->getSqlQuery($params);
+            }
+            $params = $this->convertEnums($params);
+        } else {
+            $query = $this->_view->getSelectSql();
+        }
+
+        if ($this->_type !== self::SELECT) {
+            return $this->_conn->exec($query, $params);
+        }
+
+        $stmt = $this->_conn->execute($query, $params);
+        return $stmt;
+    }
+
+    public function execute($params = array(), $hydrationMode = null)
+    {
+        $this->_preQuery();
+
+        if ($hydrationMode !== null) {
+            $this->_hydrator->setHydrationMode($hydrationMode);
+        }
+
+        $params = $this->getParams($params);
+
+        if ($this->_resultCache && $this->_type == self::SELECT) {
+            $cacheDriver = $this->getResultCacheDriver();
+
+            $dql = $this->getDql();
+            // calculate hash for dql query
+            $hash = md5($dql . var_export($params, true));
+
+            $cached = ($this->_expireResultCache) ? false : $cacheDriver->fetch($hash);
+
+            if ($cached === false) {
+                // cache miss
+                $stmt = $this->_execute($params);
+                $this->_hydrator->setQueryComponents($this->_queryComponents);
+                $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap);
+
+                $cached = $this->getCachedForm($result);
+                $cacheDriver->save($hash, $cached, $this->getResultCacheLifeSpan());
+            } else {
+                $result = $this->_constructQueryFromCache($cached);
+            }
+        } else {
+            $stmt = $this->_execute($params);
+
+            if (is_integer($stmt)) {
+                $result = $stmt;
+            } else {
+                $this->_hydrator->setQueryComponents($this->_queryComponents);
+                $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap);
+            }
+        }
+
+        return $result;
+    }
+
+    protected function _preQuery()
+    {
+        // Invoke preQuery() hook on IPF_ORM_Query for child classes which implement this hook
+        $this->preQuery();
+    }
+
+    public function preQuery()
+    {
+    }
+
+    protected function _constructQueryFromCache($cached)
+    {
+        $cached = unserialize($cached);
+        $this->_tableAliasMap = $cached[2];
+        $customComponent = $cached[0];
+
+        $queryComponents = array();
+        $cachedComponents = $cached[1];
+        foreach ($cachedComponents as $alias => $components) {
+            $e = explode('.', $components[0]);
+            if (count($e) === 1) {
+                $queryComponents[$alias]['table'] = $this->_conn->getTable($e[0]);
+            } else {
+                $queryComponents[$alias]['parent'] = $e[0];
+                $queryComponents[$alias]['relation'] = $queryComponents[$e[0]]['table']->getRelation($e[1]);
+                $queryComponents[$alias]['table'] = $queryComponents[$alias]['relation']->getTable();
+            }
+            if (isset($components[1])) {
+                $queryComponents[$alias]['agg'] = $components[1];
+            }
+            if (isset($components[2])) {
+                $queryComponents[$alias]['map'] = $components[2];
+            }
+        }
+        $this->_queryComponents = $queryComponents;
+
+        return $customComponent;
+    }
+
+    public function getCachedForm($customComponent = null)
+    {
+        $componentInfo = array();
+
+        foreach ($this->getQueryComponents() as $alias => $components) {
+            if ( ! isset($components['parent'])) {
+                $componentInfo[$alias][] = $components['table']->getComponentName();
+            } else {
+                $componentInfo[$alias][] = $components['parent'] . '.' . $components['relation']->getAlias();
+            }
+            if (isset($components['agg'])) {
+                $componentInfo[$alias][] = $components['agg'];
+            }
+            if (isset($components['map'])) {
+                $componentInfo[$alias][] = $components['map'];
+            }
+        }
+
+        return serialize(array($customComponent, $componentInfo, $this->getTableAliasMap()));
+    }
+
+    public function addSelect($select)
+    {
+        return $this->_addDqlQueryPart('select', $select, true);
+    }
+
+    public function addTableAlias($tableAlias, $componentAlias)
+    {
+        return $this->addSqlTableAlias($tableAlias, $componentAlias);
+    }
+
+    public function addSqlTableAlias($sqlTableAlias, $componentAlias)
+    {
+        $this->_tableAliasMap[$sqlTableAlias] = $componentAlias;
+        return $this;
+    }
+
+    public function addFrom($from)
+    {
+        return $this->_addDqlQueryPart('from', $from, true);
+    }
+
+    public function addWhere($where, $params = array())
+    {
+        if (is_array($params)) {
+            $this->_params['where'] = array_merge($this->_params['where'], $params);
+        } else {
+            $this->_params['where'][] = $params;
+        }
+        return $this->_addDqlQueryPart('where', $where, true);
+    }
+
+    public function whereIn($expr, $params = array(), $not = false)
+    {
+        $params = (array) $params;
+
+        // if there's no params, return (else we'll get a WHERE IN (), invalid SQL)
+        if (!count($params))
+          return $this;
+
+        $a = array();
+        foreach ($params as $k => $value) {
+            if ($value instanceof IPF_ORM_Expression) {
+                $value = $value->getSql();
+                unset($params[$k]);
+            } else {
+                $value = '?';
+            }
+            $a[] = $value;
+        }
+
+        $this->_params['where'] = array_merge($this->_params['where'], $params);
+
+        $where = $expr . ($not === true ? ' NOT ':'') . ' IN (' . implode(', ', $a) . ')';
+
+        return $this->_addDqlQueryPart('where', $where, true);
+    }
+
+    public function whereNotIn($expr, $params = array())
+    {
+        return $this->whereIn($expr, $params, true);
+    }
+
+    public function addGroupBy($groupby)
+    {
+        return $this->_addDqlQueryPart('groupby', $groupby, true);
+    }
+
+    public function addHaving($having, $params = array())
+    {
+        if (is_array($params)) {
+            $this->_params['having'] = array_merge($this->_params['having'], $params);
+        } else {
+            $this->_params['having'][] = $params;
+        }
+        return $this->_addDqlQueryPart('having', $having, true);
+    }
+
+    public function addOrderBy($orderby)
+    {
+        return $this->_addDqlQueryPart('orderby', $orderby, true);
+    }
+
+    public function select($select)
+    {
+        return $this->_addDqlQueryPart('select', $select);
+    }
+
+    public function distinct($flag = true)
+    {
+        $this->_sqlParts['distinct'] = (bool) $flag;
+        return $this;
+    }
+
+    public function forUpdate($flag = true)
+    {
+        $this->_sqlParts['forUpdate'] = (bool) $flag;
+        return $this;
+    }
+
+    public function delete()
+    {
+        $this->_type = self::DELETE;
+        return $this;
+    }
+
+    public function update($update)
+    {
+        $this->_type = self::UPDATE;
+        return $this->_addDqlQueryPart('from', $update);
+    }
+
+    public function set($key, $value, $params = null)
+    {
+        if (is_array($key)) {
+            foreach ($key as $k => $v) {
+                $this->set($k, '?', array($v));
+            }
+            return $this;
+        } else {
+            if ($params !== null) {
+                if (is_array($params)) {
+                    $this->_params['set'] = array_merge($this->_params['set'], $params);
+                } else {
+                    $this->_params['set'][] = $params;
+                }
+            }
+
+            $this->_pendingSetParams[$key] = $value;
+
+            return $this->_addDqlQueryPart('set', $key . ' = ' . $value, true);
+        }
+    }
+
+    public function from($from)
+    {
+        return $this->_addDqlQueryPart('from', $from);
+    }
+
+    public function innerJoin($join, $params = array())
+    {
+        if (is_array($params)) {
+            $this->_params['join'] = array_merge($this->_params['join'], $params);
+        } else {
+            $this->_params['join'][] = $params;
+        }
+
+        return $this->_addDqlQueryPart('from', 'INNER JOIN ' . $join, true);
+    }
+
+    public function leftJoin($join, $params = array())
+    {
+        if (is_array($params)) {
+            $this->_params['join'] = array_merge($this->_params['join'], $params);
+        } else {
+            $this->_params['join'][] = $params;
+        }
+
+        return $this->_addDqlQueryPart('from', 'LEFT JOIN ' . $join, true);
+    }
+
+    public function groupBy($groupby)
+    {
+        return $this->_addDqlQueryPart('groupby', $groupby);
+    }
+
+    public function where($where, $params = array())
+    {
+        $this->_params['where'] = array();
+        if (is_array($params)) {
+            $this->_params['where'] = $params;
+        } else {
+            $this->_params['where'][] = $params;
+        }
+
+        return $this->_addDqlQueryPart('where', $where);
+    }
+
+    public function having($having, $params = array())
+    {
+        $this->_params['having'] = array();
+        if (is_array($params)) {
+            $this->_params['having'] = $params;
+        } else {
+            $this->_params['having'][] = $params;
+        }
+
+        return $this->_addDqlQueryPart('having', $having);
+    }
+
+    public function orderBy($orderby)
+    {
+        return $this->_addDqlQueryPart('orderby', $orderby);
+    }
+
+    public function limit($limit)
+    {
+        return $this->_addDqlQueryPart('limit', $limit);
+    }
+
+    public function offset($offset)
+    {
+        return $this->_addDqlQueryPart('offset', $offset);
+    }
+
+    public function getSql($params = array())
+    {
+        return $this->getSqlQuery($params);
+    }
+
+    protected function clear()
+    {
+        $this->_sqlParts = array(
+                    'select'    => array(),
+                    'distinct'  => false,
+                    'forUpdate' => false,
+                    'from'      => array(),
+                    'set'       => array(),
+                    'join'      => array(),
+                    'where'     => array(),
+                    'groupby'   => array(),
+                    'having'    => array(),
+                    'orderby'   => array(),
+                    'limit'     => false,
+                    'offset'    => false,
+                    );
+    }
+
+    public function setHydrationMode($hydrationMode)
+    {
+        $this->_hydrator->setHydrationMode($hydrationMode);
+        return $this;
+    }
+
+    public function getAliasMap()
+    {
+        return $this->_queryComponents;
+    }
+
+    public function getQueryComponents()
+    {
+        return $this->_queryComponents;
+    }
+
+    public function getParts()
+    {
+        return $this->getSqlParts();
+    }
+
+    public function getSqlParts()
+    {
+        return $this->_sqlParts;
+    }
+
+    public function getType()
+    {
+        return $this->_type;
+    }
+
+    public function useCache($driver = true, $timeToLive = null)
+    {
+        return $this->useResultCache($driver, $timeToLive);
+    }
+
+    public function useResultCache($driver = true, $timeToLive = null)
+    {
+        if ($driver !== null && $driver !== true && ! ($driver instanceOf IPF_ORM_Cache_Interface)) {
+            $msg = 'First argument should be instance of IPF_ORM_Cache_Interface or null.';
+            throw new IPF_ORM_Exception($msg);
+        }
+        $this->_resultCache = $driver;
+
+        return $this->setResultCacheLifeSpan($timeToLive);
+    }
+
+    public function useQueryCache(IPF_ORM_Cache_Interface $driver, $timeToLive = null)
+    {
+        $this->_queryCache = $driver;
+        return $this->setQueryCacheLifeSpan($timeToLive);
+    }
+
+    public function expireCache($expire = true)
+    {
+        return $this->expireResultCache($expire);
+    }
+
+    public function expireResultCache($expire = true)
+    {
+        $this->_expireResultCache = true;
+        return $this;
+    }
+
+    public function expireQueryCache($expire = true)
+    {
+        $this->_expireQueryCache = true;
+        return $this;
+    }
+
+    public function setCacheLifeSpan($timeToLive)
+    {
+        return $this->setResultCacheLifeSpan($timeToLive);
+    }
+
+    public function setResultCacheLifeSpan($timeToLive)
+    {
+        if ($timeToLive !== null) {
+            $timeToLive = (int) $timeToLive;
+        }
+        $this->_resultCacheTTL = $timeToLive;
+
+        return $this;
+    }
+
+    public function getResultCacheLifeSpan()
+    {
+        return $this->_resultCacheTTL;
+    }
+
+    public function setQueryCacheLifeSpan($timeToLive)
+    {
+        if ($timeToLive !== null) {
+            $timeToLive = (int) $timeToLive;
+        }
+        $this->_queryCacheTTL = $timeToLive;
+
+        return $this;
+    }
+
+    public function getQueryCacheLifeSpan()
+    {
+        return $this->_queryCacheTTL;
+    }
+
+    public function getCacheDriver()
+    {
+        return $this->getResultCacheDriver();
+    }
+
+    public function getResultCacheDriver()
+    {
+        if ($this->_resultCache instanceof IPF_ORM_Cache_Interface) {
+            return $this->_resultCache;
+        } else {
+            return $this->_conn->getResultCacheDriver();
+        }
+    }
+
+    public function getQueryCacheDriver()
+    {
+        if ($this->_queryCache instanceof IPF_ORM_Cache_Interface) {
+            return $this->_queryCache;
+        } else {
+            return $this->_conn->getQueryCacheDriver();
+        }
+    }
+
+    public function getConnection()
+    {
+        return $this->_conn;
+    }
+
+    protected function _addDqlQueryPart($queryPartName, $queryPart, $append = false)
+    {
+        if ($append) {
+            $this->_dqlParts[$queryPartName][] = $queryPart;
+        } else {
+            $this->_dqlParts[$queryPartName] = array($queryPart);
+        }
+
+        $this->_state = IPF_ORM_Query::STATE_DIRTY;
+        return $this;
+    }
+
+    protected function _processDqlQueryPart($queryPartName, $queryParts)
+    {
+        $this->removeSqlQueryPart($queryPartName);
+
+        if (is_array($queryParts) && ! empty($queryParts)) {
+            foreach ($queryParts as $queryPart) {
+                $parser = $this->_getParser($queryPartName);
+                $sql = $parser->parse($queryPart);
+                if (isset($sql)) {
+                    if ($queryPartName == 'limit' || $queryPartName == 'offset') {
+                        $this->setSqlQueryPart($queryPartName, $sql);
+                    } else {
+                        $this->addSqlQueryPart($queryPartName, $sql);
+                    }
+                }
+            }
+        }
+    }
+
+    protected function _getParser($name)
+    {
+        if ( ! isset($this->_parsers[$name])) {
+            $class = 'IPF_ORM_Query_' . ucwords(strtolower($name));
+
+            //IPF_ORM::autoload($class);
+
+            if ( ! class_exists($class)) {
+                throw new IPF_ORM_Exception('Unknown parser ' . $name);
+            }
+
+            $this->_parsers[$name] = new $class($this, $this->_tokenizer);
+        }
+
+        return $this->_parsers[$name];
+    }
+
+    abstract public function getSqlQuery($params = array());
+
+    abstract public function parseDqlQuery($query);
+
+    public function parseQuery($query)
+    {
+        return $this->parseDqlQuery($query);
+    }
+
+    public function getQuery($params = array())
+    {
+        return $this->getSqlQuery($params);
+    }
+
+    public function __debugInfo()
+    {
+        return array('sql' => $this->getQuery());
+    }
+}
+
diff --git a/src/orm/query/check.php b/src/orm/query/check.php
new file mode 100644 (file)
index 0000000..79728c3
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+
+class IPF_ORM_Query_Check
+{
+    protected $table;
+
+    protected $sql;
+    
+    protected $_tokenizer;
+
+    public function __construct($table)
+    {
+        if ( ! ($table instanceof IPF_ORM_Table)) {
+            $table = IPF_ORM_Manager::connection()->getTable($table);
+        }
+        $this->table = $table;
+        $this->_tokenizer = new IPF_ORM_Query_Tokenizer();
+    }
+
+    public function getTable()
+    {
+        return $this->table;
+    }
+
+    public function parse($dql)
+    {
+        $this->sql = $this->parseClause($dql);
+    }
+
+    public function parseClause($dql)
+    {
+        $parts = $this->_tokenizer->sqlExplode($dql, ' AND ');
+
+        if (count($parts) > 1) {
+            $ret = array();
+            foreach ($parts as $part) {
+                $ret[] = $this->parseSingle($part);
+            }
+
+            $r = implode(' AND ', $ret);
+        } else {
+            $parts = $this->_tokenizer->quoteExplode($dql, ' OR ');
+            if (count($parts) > 1) {
+                $ret = array();
+                foreach ($parts as $part) {
+                    $ret[] = $this->parseClause($part);
+                }
+
+                $r = implode(' OR ', $ret);
+            } else {
+                $ret = $this->parseSingle($dql);
+                return $ret;
+            }
+        }
+        return '(' . $r . ')';
+    }
+    
+    public function parseSingle($part)
+    {
+        $e = explode(' ', $part);
+        
+        $e[0] = $this->parseFunction($e[0]);
+
+        switch ($e[1]) {
+            case '>':
+            case '<':
+            case '=':
+            case '!=':
+            case '<>':
+
+            break;
+            default:
+                throw new IPF_ORM_Exception('Unknown operator ' . $e[1]);
+        }
+
+        return implode(' ', $e);
+    }
+    public function parseFunction($dql) 
+    {
+        if (($pos = strpos($dql, '(')) !== false) {
+            $func  = substr($dql, 0, $pos);
+            $value = substr($dql, ($pos + 1), -1);
+            
+            $expr  = $this->table->getConnection()->expression;
+
+            if ( ! method_exists($expr, $func)) {
+                throw new IPF_ORM_Exception('Unknown function ' . $func);
+            }
+            
+            $func  = $expr->$func($value);
+        }
+        return $func;
+    }
+
+    public function getSql()
+    {
+        return $this->sql;
+    }
+}
diff --git a/src/orm/query/condition.php b/src/orm/query/condition.php
new file mode 100644 (file)
index 0000000..b18e9eb
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+
+abstract class IPF_ORM_Query_Condition extends IPF_ORM_Query_Part
+{
+    public function parse($str)
+    {
+        $tmp = trim($str);
+
+        $parts = $this->_tokenizer->bracketExplode($str, array(' \&\& ', ' AND '), '(', ')');
+
+        if (count($parts) > 1) {
+            $ret = array();
+            foreach ($parts as $part) {
+                $part = $this->_tokenizer->bracketTrim($part, '(', ')');
+                $ret[] = $this->parse($part);
+            }
+            $r = implode(' AND ', $ret);
+        } else {
+
+            $parts = $this->_tokenizer->bracketExplode($str, array(' \|\| ', ' OR '), '(', ')');
+            if (count($parts) > 1) {
+                $ret = array();
+                foreach ($parts as $part) {
+                    $part = $this->_tokenizer->bracketTrim($part, '(', ')');
+                    $ret[] = $this->parse($part);
+                }
+                $r = implode(' OR ', $ret);
+            } else {
+                // Fix for #710
+                if (substr($parts[0],0,1) == '(' && substr($parts[0], -1) == ')') {
+                    return $this->parse(substr($parts[0], 1, -1));
+                } else {
+                    // Processing NOT here
+                    if (strtoupper(substr($parts[0], 0, 4)) === 'NOT ') {
+                        $r = 'NOT ('.$this->parse(substr($parts[0], 4)).')';
+                    } else {
+                        return $this->load($parts[0]);
+                    }
+                }
+            }
+        }
+
+        return '(' . $r . ')';
+    }
+
+    private function parseBoolean($value)
+    {
+        // parse booleans
+        if ($value == 'true') {
+            $value = 1;
+        } elseif ($value == 'false') {
+            $value = 0;
+        }
+        return $value;
+    }
+
+    public function parseLiteralValue($value)
+    {
+        // check that value isn't a string
+        if (strpos($value, '\'') === false) {
+            // parse booleans
+            $value = $this->parseBoolean($value);
+
+            $a = explode('.', $value);
+
+            if (count($a) > 1) {
+            // either a float or a component..
+
+                if ( ! is_numeric($a[0])) {
+                    // a component found
+                    $field     = array_pop($a);
+                         $reference = implode('.', $a);
+                    $value = $this->query->getTableAlias($reference). '.' . $field;
+                }
+            }
+        } else {
+            // string literal found
+        }
+        return $value;
+    }
+}
diff --git a/src/orm/query/filter.php b/src/orm/query/filter.php
new file mode 100644 (file)
index 0000000..65b0917
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+class IPF_ORM_Query_Filter implements IPF_ORM_Query_Filter_Interface
+{
+    public function preQuery(IPF_ORM_Query $query)
+    {
+    }
+
+    public function postQuery(IPF_ORM_Query $query)
+    {
+    }
+}
\ No newline at end of file
diff --git a/src/orm/query/filter/chain.php b/src/orm/query/filter/chain.php
new file mode 100644 (file)
index 0000000..5444395
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+class IPF_ORM_Query_Filter_Chain
+{
+    protected $_filters = array();
+
+    public function add(IPF_ORM_Query_Filter $filter)
+    {
+        $this->_filters[] = $filter;
+    }
+
+    public function get($key)
+    {
+        if ( ! isset($this->_filters[$key])) {
+            throw new IPF_ORM_Exception('Unknown filter ' . $key);
+        }
+        return $this->_filters[$key];
+    }
+
+    public function set($key, IPF_ORM_Query_Filter $listener)
+    {
+        $this->_filters[$key] = $listener;
+    }
+
+    public function preQuery(IPF_ORM_Query $query)
+    {
+        foreach ($this->_filters as $filter) {
+            $filter->preQuery($query);
+        }
+    }
+
+    public function postQuery(IPF_ORM_Query $query)
+    {
+        foreach ($this->_filters as $filter) {
+            $filter->postQuery($query);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/orm/query/filter/interface.php b/src/orm/query/filter/interface.php
new file mode 100644 (file)
index 0000000..09c104f
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+
+interface IPF_ORM_Query_Filter_Interface
+{
+    public function preQuery(IPF_ORM_Query $query);
+    public function postQuery(IPF_ORM_Query $query);
+}
\ No newline at end of file
diff --git a/src/orm/query/from.php b/src/orm/query/from.php
new file mode 100644 (file)
index 0000000..d2c2668
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+
+class IPF_ORM_Query_From extends IPF_ORM_Query_Part
+{
+    public function parse($str, $return = false)
+    {
+        $str = trim($str);
+        $parts = $this->_tokenizer->bracketExplode($str, 'JOIN');
+
+        $from = $return ? array() : null;
+
+        $operator = false;
+
+        switch (trim($parts[0])) {
+            case 'INNER':
+                $operator = ':';
+            case 'LEFT':
+                array_shift($parts);
+            break;
+        }
+
+        $last = '';
+
+        foreach ($parts as $k => $part) {
+            $part = trim($part);
+
+            if (empty($part)) {
+                continue;
+            }
+
+            $e = explode(' ', $part);
+
+            if (end($e) == 'INNER' || end($e) == 'LEFT') {
+                $last = array_pop($e);
+            }
+            $part = implode(' ', $e);
+
+            foreach ($this->_tokenizer->bracketExplode($part, ',') as $reference) {
+                $reference = trim($reference);
+                $e = explode(' ', $reference);
+                $e2 = explode('.', $e[0]);
+
+                if ($operator) {
+                    $e[0] = array_shift($e2) . $operator . implode('.', $e2);
+                }
+
+                if ($return) {
+                    $from[] = $e;
+                } else {
+                    $table = $this->query->load(implode(' ', $e));
+                }
+            }
+
+            $operator = ($last == 'INNER') ? ':' : '.';
+        }
+        return $from;
+    }
+}
diff --git a/src/orm/query/groupby.php b/src/orm/query/groupby.php
new file mode 100644 (file)
index 0000000..824f11c
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+
+class IPF_ORM_Query_Groupby extends IPF_ORM_Query_Part
+{
+    public function parse($str, $append = false)
+    {
+        $r = array();
+        foreach (explode(',', $str) as $reference) {
+            $reference = trim($reference);
+
+            $r[] = $this->query->parseClause($reference);
+        }
+        return implode(', ', $r);
+    }
+}
diff --git a/src/orm/query/having.php b/src/orm/query/having.php
new file mode 100644 (file)
index 0000000..3fe3b6d
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+
+class IPF_ORM_Query_Having extends IPF_ORM_Query_Condition
+{
+    private function parseAggregateFunction($func)
+    {
+        $pos = strpos($func, '(');
+
+        if ($pos !== false) {
+            $funcs  = array();
+
+            $name   = substr($func, 0, $pos);
+            $func   = substr($func, ($pos + 1), -1);
+            $params = $this->_tokenizer->bracketExplode($func, ',', '(', ')');
+
+            foreach ($params as $k => $param) {
+                $params[$k] = $this->parseAggregateFunction($param);
+            }
+
+            $funcs = $name . '(' . implode(', ', $params) . ')';
+
+            return $funcs;
+
+        } else {
+            if ( ! is_numeric($func)) {
+                $a = explode('.', $func);
+
+                if (count($a) > 1) {
+                    $field     = array_pop($a);
+                    $reference = implode('.', $a);
+                    $map       = $this->query->load($reference, false);
+                    $field     = $map['table']->getColumnName($field);
+                    $func      = $this->query->getTableAlias($reference) . '.' . $field;
+                } else {
+                    $field = end($a);
+                    $func  = $this->query->getAggregateAlias($field);
+                }
+                return $func;
+            } else {
+                return $func;
+            }
+        }
+    }
+
+    final public function load($having)
+    {
+        $tokens = $this->_tokenizer->bracketExplode($having, ' ', '(', ')');
+        $part = $this->parseAggregateFunction(array_shift($tokens));
+        $operator  = array_shift($tokens);
+        $value     = implode(' ', $tokens);
+        $part .= ' ' . $operator . ' ' . $value;
+        // check the RHS for aggregate functions
+        if (strpos($value, '(') !== false) {
+          $value = $this->parseAggregateFunction($value);
+        }
+        return $part;
+    }
+}
diff --git a/src/orm/query/joincondition.php b/src/orm/query/joincondition.php
new file mode 100644 (file)
index 0000000..94d969b
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+
+class IPF_ORM_Query_JoinCondition extends IPF_ORM_Query_Condition 
+{
+    public function load($condition) 
+    {
+        $condition = trim($condition);
+
+        $e = $this->_tokenizer->sqlExplode($condition);
+
+        if (count($e) > 2) {
+            $expr = new IPF_ORM_Expression($e[0], $this->query->getConnection());
+            $e[0] = $expr->getSql();
+
+            $operator  = $e[1];
+
+            if (substr(trim($e[2]), 0, 1) != '(') {
+                $expr = new IPF_ORM_Expression($e[2], $this->query->getConnection());
+                $e[2] = $expr->getSql();
+            }
+
+            // We need to check for agg functions here
+            $hasLeftAggExpression = preg_match('/(.*)\(([^\)]*)\)([\)]*)/', $e[0], $leftMatches);
+
+            if ($hasLeftAggExpression) {
+                $e[0] = $leftMatches[2];
+            }
+
+            $hasRightAggExpression = preg_match('/(.*)\(([^\)]*)\)([\)]*)/', $e[2], $rightMatches);
+
+            if ($hasRightAggExpression) {
+                $e[2] = $rightMatches[2];
+            }
+
+            $a         = explode('.', $e[0]);
+            $field     = array_pop($a);
+            $reference = implode('.', $a);
+            $value     = $e[2];
+
+            $conn      = $this->query->getConnection();
+            $alias     = $this->query->getTableAlias($reference);
+            $map       = $this->query->getAliasDeclaration($reference);
+            $table     = $map['table'];
+            // check if value is enumerated value
+            $enumIndex = $table->enumIndex($field, trim($value, "'"));
+
+            if (false !== $enumIndex && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+                $enumIndex = $conn->quote($enumIndex, 'text');
+            }
+
+            // FIX: Issues with "(" XXX ")"
+            if ($hasRightAggExpression) {
+                $value = '(' . $value . ')';
+            }
+
+            if (substr($value, 0, 1) == '(') {
+                // trim brackets
+                $trimmed   = $this->_tokenizer->bracketTrim($value);
+
+                if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') {
+                    // subquery found
+                    $q = $this->query->createSubquery();
+
+                    // Change due to bug "(" XXX ")"
+                    //$value = '(' . $q->parseQuery($trimmed)->getQuery() . ')';
+                    $value = $q->parseQuery($trimmed)->getQuery();
+                } elseif (substr($trimmed, 0, 4) == 'SQL:') {
+                    // Change due to bug "(" XXX ")"
+                    //$value = '(' . substr($trimmed, 4) . ')';
+                    $value = substr($trimmed, 4);
+                } else {
+                    // simple in expression found
+                    $e     = $this->_tokenizer->sqlExplode($trimmed, ',');
+
+                    $value = array();
+                    foreach ($e as $part) {
+                        $index = $table->enumIndex($field, trim($part, "'"));
+
+                        if (false !== $index && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+                            $index = $conn->quote($index, 'text');
+                        }
+
+                        if ($index !== false) {
+                            $value[] = $index;
+                        } else {
+                            $value[] = $this->parseLiteralValue($part);
+                        }
+                    }
+
+                    // Change due to bug "(" XXX ")"
+                    //$value = '(' . implode(', ', $value) . ')';
+                    $value = implode(', ', $value);
+                }
+            } else {
+                if ($enumIndex !== false) {
+                    $value = $enumIndex;
+                } else {
+                    $value = $this->parseLiteralValue($value);
+                }
+            }
+
+            switch ($operator) {
+                case '<':
+                case '>':
+                case '=':
+                case '!=':
+                    if ($enumIndex !== false) {
+                        $value  = $enumIndex;
+                    }
+                default:
+                    $leftExpr = (($hasLeftAggExpression) ? $leftMatches[1] . '(' : '') 
+                              . $alias . '.' . $field
+                              . (($hasLeftAggExpression) ? $leftMatches[3] . ')' : '') ;
+
+                    $rightExpr = (($hasRightAggExpression) ? $rightMatches[1] . '(' : '') 
+                              . $value
+                              . (($hasRightAggExpression) ? $rightMatches[3] . ')' : '') ;
+
+                    $condition  = $leftExpr . ' ' . $operator . ' ' . $rightExpr;
+            }
+
+        }
+
+        return $condition;
+    }
+}
diff --git a/src/orm/query/limit.php b/src/orm/query/limit.php
new file mode 100644 (file)
index 0000000..f0cd5ae
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+class IPF_ORM_Query_Limit extends IPF_ORM_Query_Part
+{
+    public function parse($limit) 
+    {
+        return (int) $limit;
+    }
+}
\ No newline at end of file
diff --git a/src/orm/query/offset.php b/src/orm/query/offset.php
new file mode 100644 (file)
index 0000000..9f32a62
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+class IPF_ORM_Query_Offset extends IPF_ORM_Query_Part
+{
+    public function parse($offset)
+    {
+        return (int) $offset;
+    }
+}
\ No newline at end of file
diff --git a/src/orm/query/orderby.php b/src/orm/query/orderby.php
new file mode 100644 (file)
index 0000000..5b04cda
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+class IPF_ORM_Query_Orderby extends IPF_ORM_Query_Part
+{
+    public function parse($str, $append = false)
+    {
+        $ret = array();
+
+        foreach (explode(',', trim($str)) as $r) {
+            $r = $this->query->parseClause($r);
+
+            $ret[] = $r;
+        }
+        return $ret;
+    }
+}
diff --git a/src/orm/query/parser.php b/src/orm/query/parser.php
new file mode 100644 (file)
index 0000000..71d6f1b
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+
+class IPF_ORM_Query_Parser 
+{
+}
\ No newline at end of file
diff --git a/src/orm/query/part.php b/src/orm/query/part.php
new file mode 100644 (file)
index 0000000..b9975bf
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+abstract class IPF_ORM_Query_Part
+{
+    protected $query;
+    
+    protected $_tokenizer;
+
+    public function __construct($query, IPF_ORM_Query_Tokenizer $tokenizer = null)
+    {
+        $this->query = $query;
+        if ( ! $tokenizer) {
+            $tokenizer = new IPF_ORM_Query_Tokenizer();
+        }
+        $this->_tokenizer = $tokenizer;
+    }
+
+    public function getQuery()
+    {
+        return $this->query;
+    }
+}
diff --git a/src/orm/query/registry.php b/src/orm/query/registry.php
new file mode 100644 (file)
index 0000000..f35ff42
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+class IPF_ORM_Query_Registry
+{
+    protected $_queries = array();
+
+    public function add($key, $query)
+    {
+       if (strpos($key, '/') === false) {
+            $this->_queries[$key] = $query;
+        } else {
+            // namespace found
+            $e = explode('/', $key);
+
+            $this->_queries[$e[0]][$e[1]] = $query;
+        }
+    }
+    
+    public function get($key, $namespace = null)
+    {
+        if (isset($namespace)) {
+            if ( ! isset($this->_queries[$namespace][$key])) {
+                throw new IPF_ORM_Exception('A query with the name ' . $namespace . '/' . $key . ' does not exist.');
+            }
+            $query = $this->_queries[$namespace][$key];
+        } else {
+            if ( ! isset($this->_queries[$key])) {
+                throw new IPF_ORM_Exception('A query with the name ' . $key . ' does not exist.');
+            }
+            $query = $this->_queries[$key];
+        }
+        
+        if ( ! ($query instanceof IPF_ORM_Query)) {
+            $query = IPF_ORM_Query::create()->parseQuery($query);
+        }
+        
+        return $query;
+    }
+}
\ No newline at end of file
diff --git a/src/orm/query/select.php b/src/orm/query/select.php
new file mode 100644 (file)
index 0000000..0994a68
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+class IPF_ORM_Query_Select extends IPF_ORM_Query_Part
+{
+    public function parse($dql) 
+    {
+        $this->query->parseSelect($dql);
+    }
+}
\ No newline at end of file
diff --git a/src/orm/query/set.php b/src/orm/query/set.php
new file mode 100644 (file)
index 0000000..5e26e3b
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+class IPF_ORM_Query_Set extends IPF_ORM_Query_Part
+{
+    public function parse($dql)
+    {
+       $terms = $this->_tokenizer->sqlExplode($dql, ' ');
+       foreach ($terms as $term) {
+            preg_match_all("/[a-z0-9_]+\.[a-z0-9_]+[\.[a-z0-9]+]*/i", $term, $m);
+            
+            if (isset($m[0])) {
+                foreach ($m[0] as $part) {
+                    $e = explode('.', trim($part));
+                    $field = array_pop($e);
+        
+                    $reference = implode('.', $e);
+        
+                    $alias = $this->query->getTableAlias($reference);
+                    $map   = $this->query->getAliasDeclaration($reference);
+        
+                    $dql = str_replace($part, $map['table']->getColumnName($field), $dql);
+                }
+            }
+        }
+        return $dql;
+    }
+}
\ No newline at end of file
diff --git a/src/orm/query/tokenizer.php b/src/orm/query/tokenizer.php
new file mode 100644 (file)
index 0000000..f9a7d13
--- /dev/null
@@ -0,0 +1,235 @@
+<?php
+
+class IPF_ORM_Query_Tokenizer
+{
+    public function tokenizeQuery($query)
+    {
+        $parts = array();
+        $tokens = $this->sqlExplode($query, ' ');
+
+        foreach ($tokens as $index => $token) {
+            $token = trim($token);
+            switch (strtolower($token)) {
+                case 'delete':
+                case 'update':
+                case 'select':
+                case 'set':
+                case 'from':
+                case 'where':
+                case 'limit':
+                case 'offset':
+                case 'having':
+                    $p = $token;
+                    //$parts[$token] = array();
+                    $parts[$token] = '';
+                break;
+                case 'order':
+                case 'group':
+                    $i = ($index + 1);
+                    if (isset($tokens[$i]) && strtolower($tokens[$i]) === 'by') {
+                        $p = $token;
+                        $parts[$token] = '';
+                        //$parts[$token] = array();
+                    } else {
+                        $parts[$p] .= "$token ";
+                        //$parts[$p][] = $token;
+                    }
+                break;
+                case 'by':
+                    continue;
+                default:
+                    if ( ! isset($p)) {
+                        throw new IPF_ORM_Exception(
+                                "Couldn't tokenize query. Encountered invalid token: '$token'.");
+                    }
+
+                    $parts[$p] .= "$token ";
+                    //$parts[$p][] = $token;
+            }
+        }
+        return $parts;
+    }
+
+    public function bracketTrim($str, $e1 = '(', $e2 = ')')
+    {
+        if (substr($str, 0, 1) === $e1 && substr($str, -1) === $e2) {
+            return substr($str, 1, -1);
+        } else {
+            return $str;
+        }
+    }
+
+    public function bracketExplode($str, $d = ' ', $e1 = '(', $e2 = ')')
+    {
+        if (is_array($d)) {
+            $a = preg_split('#('.implode('|', $d).')#i', $str);
+            $d = stripslashes($d[0]);
+        } else {
+            $a = explode($d, $str);
+        }
+
+        $i = 0;
+        $term = array();
+        foreach($a as $key=>$val) {
+            if (empty($term[$i])) {
+                $term[$i] = trim($val);
+                $s1 = substr_count($term[$i], $e1);
+                $s2 = substr_count($term[$i], $e2);
+
+                if ($s1 == $s2) {
+                    $i++;
+                }
+            } else {
+                $term[$i] .= $d . trim($val);
+                $c1 = substr_count($term[$i], $e1);
+                $c2 = substr_count($term[$i], $e2);
+
+                if ($c1 == $c2) {
+                    $i++;
+                }
+            }
+        }
+        return $term;
+    }
+
+    public function quoteExplode($str, $d = ' ')
+    {
+        if (is_array($d)) {
+            $a = preg_split('/('.implode('|', $d).')/', $str);
+            $d = stripslashes($d[0]);
+        } else {
+            $a = explode($d, $str);
+        }
+
+        $i = 0;
+        $term = array();
+        foreach ($a as $key => $val) {
+            if (empty($term[$i])) {
+                $term[$i] = trim($val);
+
+                if ( ! (substr_count($term[$i], "'") & 1)) {
+                    $i++;
+                }
+            } else {
+                $term[$i] .= $d . trim($val);
+
+                if ( ! (substr_count($term[$i], "'") & 1)) {
+                    $i++;
+                }
+            }
+        }
+        return $term;
+    }
+
+    public function sqlExplode($str, $d = ' ', $e1 = '(', $e2 = ')')
+    {
+        if ($d == ' ') {
+            $d = array(' ', '\s');
+        }
+        if (is_array($d)) {
+            $d = array_map('preg_quote', $d);
+
+            if (in_array(' ', $d)) {
+                $d[] = '\s';
+            }
+
+            $split = '#(' . implode('|', $d) . ')#';
+
+            $str = preg_split($split, $str);
+            $d = stripslashes($d[0]);
+        } else {
+            $str = explode($d, $str);
+        }
+
+        $i = 0;
+        $term = array();
+
+        foreach ($str as $key => $val) {
+            if (empty($term[$i])) {
+                $term[$i] = trim($val);
+
+                $s1 = substr_count($term[$i], $e1);
+                $s2 = substr_count($term[$i], $e2);
+
+                if (strpos($term[$i], '(') !== false) {
+                    if ($s1 == $s2) {
+                        $i++;
+                    }
+                } else {
+                    if ( ! (substr_count($term[$i], "'") & 1) &&
+                         ! (substr_count($term[$i], "\"") & 1)) {
+                        $i++;
+                    }
+                }
+            } else {
+                $term[$i] .= $d . trim($val);
+                $c1 = substr_count($term[$i], $e1);
+                $c2 = substr_count($term[$i], $e2);
+
+                if (strpos($term[$i], '(') !== false) {
+                    if ($c1 == $c2) {
+                        $i++;
+                    }
+                } else {
+                    if ( ! (substr_count($term[$i], "'") & 1) &&
+                         ! (substr_count($term[$i], "\"") & 1)) {
+                        $i++;
+                    }
+                }
+            }
+        }
+        return $term;
+    }
+
+    public function clauseExplode($str, array $d, $e1 = '(', $e2 = ')')
+    {
+        if (is_array($d)) {
+            $d = array_map('preg_quote', $d);
+
+            if (in_array(' ', $d)) {
+                $d[] = '\s';
+            }
+
+            $split = '#(' . implode('|', $d) . ')#';
+
+            $str = preg_split($split, $str, -1, PREG_SPLIT_DELIM_CAPTURE);
+        }
+
+        $i = 0;
+        $term = array();
+
+        foreach ($str as $key => $val) {
+            if ($key & 1) {
+                if (isset($term[($i - 1)]) && ! is_array($term[($i - 1)])) {
+                    $term[($i - 1)] = array($term[($i - 1)], $val);
+                }
+                continue;
+            }
+            if (empty($term[$i])) {
+                $term[$i] = $val;
+            } else {
+                $term[$i] .= $str[($key - 1)] . $val;
+            }
+
+            $c1 = substr_count($term[$i], $e1);
+            $c2 = substr_count($term[$i], $e2);
+
+            if (strpos($term[$i], '(') !== false) {
+                if ($c1 == $c2) {
+                    $i++;
+                }
+            } else {
+                if ( ! (substr_count($term[$i], "'") & 1) &&
+                     ! (substr_count($term[$i], "\"") & 1)) {
+                    $i++;
+                }
+            }
+        }
+
+        if (isset($term[$i - 1])) {
+            $term[$i - 1] = array($term[$i - 1], '');
+        }
+
+        return $term;
+    }
+}
diff --git a/src/orm/query/where.php b/src/orm/query/where.php
new file mode 100644 (file)
index 0000000..1f51bbc
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+
+class IPF_ORM_Query_Where extends IPF_ORM_Query_Condition
+{
+    public function load($where) 
+    {
+        $where = $this->_tokenizer->bracketTrim(trim($where));
+        $conn  = $this->query->getConnection();
+        $terms = $this->_tokenizer->sqlExplode($where);  
+
+        if (count($terms) > 1) {
+            if (substr($where, 0, 6) == 'EXISTS') {
+                return $this->parseExists($where, true);
+            } elseif (substr($where, 0, 10) == 'NOT EXISTS') {
+                return $this->parseExists($where, false);
+            }
+        }
+
+        if (count($terms) < 3) {
+            $terms = $this->_tokenizer->sqlExplode($where, array('=', '<', '<>', '>', '!='));
+        }
+
+        if (count($terms) > 1) {
+            $first = array_shift($terms);
+            $value = array_pop($terms);
+            $operator = trim(substr($where, strlen($first), -strlen($value)));
+            $table = null;
+            $field = null;
+
+            if (strpos($first, "'") === false && strpos($first, '(') === false) {
+                // normal field reference found
+                $a = explode('.', $first);
+        
+                $field = array_pop($a);
+                $reference = implode('.', $a);
+                
+                if (empty($reference)) {
+                    $map = $this->query->getRootDeclaration();  
+                    
+                    $alias = $this->query->getTableAlias($this->query->getRootAlias());
+                    $table = $map['table'];
+                } else {
+                    $map = $this->query->load($reference, false);
+    
+                    $alias = $this->query->getTableAlias($reference);
+                    $table = $map['table'];
+                }
+            }
+            $first = $this->query->parseClause($first);
+            
+            $sql = $first . ' ' . $operator . ' ' . $this->parseValue($value, $table, $field);
+        
+            return $sql;  
+        } else {
+            return $where;
+        }
+    }
+
+    public function parseValue($value, IPF_ORM_Table $table = null, $field = null)
+    {
+        $conn = $this->query->getConnection();
+
+        if (substr($value, 0, 1) == '(') {
+            // trim brackets
+            $trimmed = $this->_tokenizer->bracketTrim($value);
+
+            if (substr($trimmed, 0, 4) == 'FROM' ||
+                substr($trimmed, 0, 6) == 'SELECT') {
+
+                // subquery found
+                $q     = new IPF_ORM_Query();
+                $value = '(' . $this->query->createSubquery()->parseQuery($trimmed, false)->getQuery() . ')';
+
+            } elseif (substr($trimmed, 0, 4) == 'SQL:') {
+                $value = '(' . substr($trimmed, 4) . ')';
+            } else {
+                // simple in expression found
+                $e = $this->_tokenizer->sqlExplode($trimmed, ',');
+
+                $value = array();
+
+                $index = false;
+
+                foreach ($e as $part) {
+                    if (isset($table) && isset($field)) {
+                        $index = $table->enumIndex($field, trim($part, "'"));
+
+                        if (false !== $index && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+                            $index = $conn->quote($index, 'text');
+                        }
+                    }
+
+                    if ($index !== false) {
+                        $value[] = $index;
+                    } else {
+                        $value[] = $this->parseLiteralValue($part);
+                    }
+                }
+
+                $value = '(' . implode(', ', $value) . ')';
+            }
+        } else if (substr($value, 0, 1) == ':' || $value === '?') {
+            // placeholder found
+            if (isset($table) && isset($field) && $table->getTypeOf($field) == 'enum') {
+                $this->query->addEnumParam($value, $table, $field);
+            } else {
+                $this->query->addEnumParam($value, null, null);
+            }
+        } else {
+            $enumIndex = false;
+            if (isset($table) && isset($field)) {
+                // check if value is enumerated value
+                $enumIndex = $table->enumIndex($field, trim($value, "'"));
+
+                if (false !== $enumIndex && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+                    $enumIndex = $conn->quote($enumIndex, 'text');
+                }
+            }
+
+            if ($enumIndex !== false) {
+                $value = $enumIndex;
+            } else {
+                $value = $this->parseLiteralValue($value);
+            }
+        }
+        return $value;
+    }
+
+    public function parseExists($where, $negation)
+    {
+        $operator = ($negation) ? 'EXISTS' : 'NOT EXISTS';
+
+        $pos = strpos($where, '(');
+
+        if ($pos == false) {
+            throw new IPF_ORM_Exception('Unknown expression, expected a subquery with () -marks');
+        }
+
+        $sub = $this->_tokenizer->bracketTrim(substr($where, $pos));
+
+        return $operator . ' (' . $this->query->createSubquery()->parseQuery($sub, false)->getQuery() . ')';
+    }
+}
diff --git a/src/orm/rawsql.php b/src/orm/rawsql.php
new file mode 100644 (file)
index 0000000..fce5016
--- /dev/null
@@ -0,0 +1,233 @@
+<?php
+
+class IPF_ORM_RawSql extends IPF_ORM_Query_Abstract
+{
+    private $fields = array();
+
+    public function parseDqlQueryPart($queryPartName, $queryPart, $append=false)
+    {
+        if ($queryPartName == 'select') {
+            $this->_parseSelectFields($queryPart, $append);
+            return $this;
+        }
+
+        if (!isset($this->parts[$queryPartName])) {
+            $this->_sqlParts[$queryPartName] = array();
+        }
+
+        if (!$append) {
+            $this->_sqlParts[$queryPartName] = array($queryPart);
+        } else {
+            $this->_sqlParts[$queryPartName][] = $queryPart;
+        }
+        return $this;
+    }
+
+    protected function _addDqlQueryPart($queryPartName, $queryPart, $append=false)
+    {
+        return $this->parseDqlQueryPart($queryPartName, $queryPart, $append);
+    }
+
+    private function _parseSelectFields($queryPart, $append=false)
+    {
+        if ($append)
+            $this->fields[] = $queryPart;
+        else
+            $this->fields = array($queryPart);
+
+        $this->_sqlParts['select'] = array();
+    }
+
+    public function parseDqlQuery($query)
+    {
+        $this->_parseSelectFields($query);
+        $this->clear();
+
+        $tokens = $this->_tokenizer->sqlExplode($query, ' ');
+
+        $parts = array();
+        foreach ($tokens as $key => $part) {
+            $partLowerCase = strtolower($part);
+            switch ($partLowerCase) {
+                case 'select':
+                case 'from':
+                case 'where':
+                case 'limit':
+                case 'offset':
+                case 'having':
+                    $type = $partLowerCase;
+                    if ( ! isset($parts[$partLowerCase])) {
+                        $parts[$partLowerCase] = array();
+                    }
+                    break;
+                case 'order':
+                case 'group':
+                    $i = $key + 1;
+                    if (isset($tokens[$i]) && strtolower($tokens[$i]) === 'by') {
+                        $type = $partLowerCase . 'by';
+                        $parts[$type] = array();
+                    } else {
+                        //not a keyword so we add it to the previous type
+                        $parts[$type][] = $part;
+                    }
+                    break;
+                case 'by':
+                    continue;
+                default:
+                    //not a keyword so we add it to the previous type.
+                    if ( ! isset($parts[$type][0])) {
+                        $parts[$type][0] = $part;
+                    } else {
+                        // why does this add to index 0 and not append to the 
+                        // array. If it had done that one could have used 
+                        // parseQueryPart.
+                        $parts[$type][0] .= ' '.$part;
+                    }
+            }
+        }
+
+        $this->_sqlParts = $parts;
+        $this->_sqlParts['select'] = array();
+
+        return $this;
+    }
+
+    public function getSqlQuery($params=array())
+    {
+        $select = array();
+
+        foreach ($this->fields as $field) {
+            if (preg_match('/^{([^}{]+)\.([^}{]+)}$/U', $field, $e)) {
+                // try to auto-add component
+                if (!$this->hasSqlTableAlias($e[1])) {
+                    try {
+                        $this->addComponent($e[1], ucwords($e[1]));
+                    } catch (IPF_ORM_Exception $exception) {
+                        throw new IPF_ORM_Exception('The associated component for table alias ' . $e[1] . ' couldn\'t be found.');
+                    }
+                }
+
+                $componentAlias = $this->getComponentAlias($e[1]);
+                
+                if ($e[2] == '*') {
+                    foreach ($this->_queryComponents[$componentAlias]['table']->getColumnNames() as $name) {
+                        $field = $e[1] . '.' . $name;
+
+                        $select[$componentAlias][$field] = $field . ' AS ' . $e[1] . '__' . $name;
+                    }
+                } else {
+                    $field = $e[1] . '.' . $e[2];
+                    $select[$componentAlias][$field] = $field . ' AS ' . $e[1] . '__' . $e[2];
+                }
+            } else {
+                $select['__raw__'][] = $field;
+            }
+        }
+
+        // force-add all primary key fields
+
+        foreach ($this->getTableAliasMap() as $tableAlias => $componentAlias) {
+            $map = $this->_queryComponents[$componentAlias];
+
+            foreach ((array) $map['table']->getIdentifierColumnNames() as $key) {
+                $field = $tableAlias . '.' . $key;
+
+                if ( ! isset($this->_sqlParts['select'][$field])) {
+                    $select[$componentAlias][$field] = $field . ' AS ' . $tableAlias . '__' . $key;
+                }
+            }
+        }
+        
+        // first add the fields of the root component
+        reset($this->_queryComponents);
+        $componentAlias = key($this->_queryComponents);
+
+        $q = 'SELECT ' . implode(', ', $select[$componentAlias]);
+        unset($select[$componentAlias]);
+
+        foreach ($select as $component => $fields) {
+            if ( ! empty($fields)) {
+                $q .= ', ' . implode(', ', $fields);
+            }
+        }
+
+        $string = $this->getInheritanceCondition($this->getRootAlias());
+        if ( ! empty($string)) {
+            $this->_sqlParts['where'][] = $string;
+        }
+        $copy = $this->_sqlParts;
+        unset($copy['select']);
+
+        $q .= ( ! empty($this->_sqlParts['from']))?    ' FROM '     . implode(' ', $this->_sqlParts['from']) : '';
+        $q .= ( ! empty($this->_sqlParts['where']))?   ' WHERE '    . implode(' AND ', $this->_sqlParts['where']) : '';
+        $q .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby']) : '';
+        $q .= ( ! empty($this->_sqlParts['having']))?  ' HAVING '   . implode(' AND ', $this->_sqlParts['having']) : '';
+        $q .= ( ! empty($this->_sqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby']) : '';
+        $q .= ( ! empty($this->_sqlParts['limit']))?   ' LIMIT ' . implode(' ', $this->_sqlParts['limit']) : '';
+        $q .= ( ! empty($this->_sqlParts['offset']))?  ' OFFSET ' . implode(' ', $this->_sqlParts['offset']) : '';
+
+        if ( ! empty($string)) {
+            array_pop($this->_sqlParts['where']);
+        }
+        return $q;
+    }
+
+    public function getFields()
+    {
+        return $this->fields;
+    }
+
+    public function addComponent($tableAlias, $path)
+    {
+        $tmp           = explode(' ', $path);
+        $originalAlias = (count($tmp) > 1) ? end($tmp) : null;
+
+        $e = explode('.', $tmp[0]);
+
+        $fullPath = $tmp[0];
+        $fullLength = strlen($fullPath);
+
+        $table = null;
+
+        $currPath = '';
+
+        if (isset($this->_queryComponents[$e[0]])) {
+            $table = $this->_queryComponents[$e[0]]['table'];
+            $currPath = $parent = array_shift($e);
+        }
+
+        foreach ($e as $k => $component) {
+            // get length of the previous path
+            $length = strlen($currPath);
+
+            // build the current component path
+            $currPath = ($currPath) ? $currPath . '.' . $component : $component;
+
+            $delimeter = substr($fullPath, $length, 1);
+
+            // if an alias is not given use the current path as an alias identifier
+            if (strlen($currPath) === $fullLength && isset($originalAlias)) {
+                $componentAlias = $originalAlias;
+            } else {
+                $componentAlias = $currPath;
+            }
+
+            if (!isset($table)) {
+                $table = IPF_ORM_Manager::connection()->getTable($component);
+                $this->_queryComponents[$componentAlias] = array('table' => $table);
+            } else {
+                $relation = $table->getRelation($component);
+
+                $this->_queryComponents[$componentAlias] = array('table'    => $relation->getTable(),
+                                                                 'parent'   => $parent,
+                                                                 'relation' => $relation);
+            }
+            $this->addSqlTableAlias($tableAlias, $componentAlias);
+
+            $parent = $currPath;
+        }
+
+        return $this;
+    }
+}
+
diff --git a/src/orm/record.php b/src/orm/record.php
new file mode 100644 (file)
index 0000000..8be51ab
--- /dev/null
@@ -0,0 +1,1212 @@
+<?php
+
+abstract class IPF_ORM_Record extends IPF_ORM_Record_Abstract implements Countable, IteratorAggregate, Serializable
+{
+    const STATE_DIRTY       = 1;
+    const STATE_TDIRTY      = 2;
+    const STATE_CLEAN       = 3;
+    const STATE_PROXY       = 4;
+    const STATE_TCLEAN      = 5;
+    const STATE_LOCKED     = 6;
+
+    protected $_id           = array();
+    protected $_data         = array();
+    protected $_values       = array();
+    protected $_state;
+    protected $_modified     = array();
+    protected $_errors = array();
+    protected $_references     = array();
+    protected $_pendingDeletes = array();
+    protected $_custom         = array();
+    private static $_index = 1;
+    private $_oid;
+
+    public function __construct($table = null, $isNewEntry = false)
+    {
+        if (isset($table) && $table instanceof IPF_ORM_Table) {
+            $this->_table = $table;
+            $exists = ( ! $isNewEntry);
+        } else {
+            // get the table of this class
+            $class = get_class($this);
+            $this->_table = IPF_ORM::getTable($class);
+            $exists = false;
+        }
+
+        // Check if the current connection has the records table in its registry
+        // If not this record is only used for creating table definition and setting up
+        // relations.
+        if ( ! $this->_table->getConnection()->hasTable($this->_table->getComponentName())) {
+            return;
+        }
+
+        $this->_oid = self::$_index;
+
+        self::$_index++;
+
+        // get the data array
+        $this->_data = $this->_table->getData();
+
+        // get the column count
+        $count = count($this->_data);
+
+        $this->_values = $this->cleanData($this->_data);
+
+        $this->prepareIdentifiers($exists);
+
+        if ( ! $exists) {
+            if ($count > count($this->_values)) {
+                $this->_state = IPF_ORM_Record::STATE_TDIRTY;
+            } else {
+                $this->_state = IPF_ORM_Record::STATE_TCLEAN;
+            }
+
+            // set the default values for this record
+            $this->assignDefaultValues();
+        } else {
+            $this->_state = IPF_ORM_Record::STATE_CLEAN;
+
+            if ($count < $this->_table->getColumnCount()) {
+                $this->_state  = IPF_ORM_Record::STATE_PROXY;
+            }
+        }
+
+        $repository = $this->_table->getRepository();
+        $repository->add($this);
+
+        $this->construct();
+    }
+
+    public static function _index()
+    {
+        return self::$_index;
+    }
+
+    public function construct(){}
+
+    public function getOid()
+    {
+        return $this->_oid;
+    }
+
+    public function oid()
+    {
+        return $this->_oid;
+    }
+
+    public function isValid()
+    {
+        // Clear the stack from any previous errors.
+        $this->_errors = array();
+
+        // Run validation process
+        $validator = new IPF_ORM_Validator();
+        $validator->validateRecord($this);
+        $this->validate();
+        if ($this->_state == self::STATE_TDIRTY || $this->_state == self::STATE_TCLEAN) {
+            $this->validateOnInsert();
+        } else {
+            $this->validateOnUpdate();
+        }
+
+        return count($this->_errors) === 0;
+    }
+
+    public function addError($invalidFieldName, $errorCode = 'general')
+    {
+        $this->_errors[$invalidFieldName][] = $errorCode;
+    }
+
+    public function getErrors()
+    {
+        return $this->_errors;
+    }
+
+    protected function validate(){}
+
+    protected function validateOnUpdate(){}
+
+    protected function validateOnInsert(){}
+
+    public function preSerialize($event){}
+
+    public function postSerialize($event){}
+
+    public function preUnserialize($event){}
+
+    public function postUnserialize($event){}
+
+    public function preSave($event){}
+
+    public function postSave($event){}
+
+    public function preDelete($event){}
+
+    public function postDelete($event){}
+
+    public function preUpdate($event){}
+
+    public function postUpdate($event){}
+
+    public function preInsert($event){}
+
+    public function postInsert($event){}
+
+    public function assignDefaultValues($overwrite = false)
+    {
+        if ( ! $this->_table->hasDefaultValues()) {
+            return false;
+        }
+        foreach ($this->_data as $column => $value) {
+            $default = $this->_table->getDefaultValueOf($column);
+
+            if ($default === null) {
+                continue;
+            }
+
+            if (IPF_ORM_Null::isNull($value) || $overwrite) {
+                $this->_data[$column] = $default;
+                $this->_modified[]    = $column;
+                $this->_state = IPF_ORM_Record::STATE_TDIRTY;
+            }
+        }
+    }
+
+    public function cleanData(&$data)
+    {
+        $tmp = $data;
+        $data = array();
+
+        foreach ($this->getTable()->getFieldNames() as $fieldName) {
+            if (isset($tmp[$fieldName])) {
+                $data[$fieldName] = $tmp[$fieldName];
+            } else if (array_key_exists($fieldName, $tmp)) {
+                $data[$fieldName] = IPF_ORM_Null::getInstance();
+            } else if (!isset($this->_data[$fieldName])) {
+                $data[$fieldName] = IPF_ORM_Null::getInstance();
+            }
+            unset($tmp[$fieldName]);
+        }
+
+        return $tmp;
+    }
+
+    public function hydrate(array $data)
+    {
+        $this->_values = array_merge($this->_values, $this->cleanData($data));
+        $this->_data   = array_merge($this->_data, $data);
+        $this->prepareIdentifiers(true);
+    }
+
+    private function prepareIdentifiers($exists = true)
+    {
+        switch ($this->_table->getIdentifierType()) {
+            case IPF_ORM::IDENTIFIER_AUTOINC:
+            case IPF_ORM::IDENTIFIER_NATURAL:
+                $name = $this->_table->getIdentifier();
+                if (is_array($name)) {
+                    $name = $name[0];
+                }
+                if ($exists) {
+                    if (isset($this->_data[$name]) && !IPF_ORM_Null::isNull($this->_data[$name])) {
+                        $this->_id[$name] = $this->_data[$name];
+                    }
+                }
+                break;
+            case IPF_ORM::IDENTIFIER_COMPOSITE:
+                $names = $this->_table->getIdentifier();
+
+                foreach ($names as $name) {
+                    if (IPF_ORM_Null::isNull($this->_data[$name])) {
+                        $this->_id[$name] = null;
+                    } else {
+                        $this->_id[$name] = $this->_data[$name];
+                    }
+                }
+                break;
+        }
+    }
+
+    public function serialize()
+    {
+        $event = new IPF_ORM_Event($this, IPF_ORM_Event::RECORD_SERIALIZE);
+
+        $this->preSerialize($event);
+
+        $vars = get_object_vars($this);
+
+        unset($vars['_references']);
+        unset($vars['_table']);
+        unset($vars['_errors']);
+        unset($vars['_filter']);
+
+        $name = $this->_table->getIdentifier();
+        $this->_data = array_merge($this->_data, $this->_id);
+
+        foreach ($this->_data as $k => $v) {
+            if ($v instanceof IPF_ORM_Record && $this->_table->getTypeOf($k) != 'object') {
+                unset($vars['_data'][$k]);
+            } elseif (IPF_ORM_Null::isNull($v)) {
+                unset($vars['_data'][$k]);
+            } else {
+                switch ($this->_table->getTypeOf($k)) {
+                    case 'array':
+                    case 'object':
+                        $vars['_data'][$k] = serialize($vars['_data'][$k]);
+                        break;
+                    case 'gzip':
+                        $vars['_data'][$k] = gzcompress($vars['_data'][$k]);
+                        break;
+                    case 'enum':
+                        $vars['_data'][$k] = $this->_table->enumIndex($k, $vars['_data'][$k]);
+                        break;
+                }
+            }
+        }
+
+        $str = serialize($vars);
+
+        $this->postSerialize($event);
+
+        return $str;
+    }
+
+    public function unserialize($serialized)
+    {
+        $event = new IPF_ORM_Event($this, IPF_ORM_Event::RECORD_UNSERIALIZE);
+
+        $this->preUnserialize($event);
+
+        $connection = IPF_ORM_Manager::connection();
+
+        $this->_oid = self::$_index;
+        self::$_index++;
+
+        $this->_table = $connection->getTable(get_class($this));
+
+        $array = unserialize($serialized);
+
+        foreach($array as $k => $v) {
+            $this->$k = $v;
+        }
+
+        foreach ($this->_data as $k => $v) {
+            switch ($this->_table->getTypeOf($k)) {
+                case 'array':
+                case 'object':
+                    $this->_data[$k] = unserialize($this->_data[$k]);
+                    break;
+                case 'gzip':
+                   $this->_data[$k] = gzuncompress($this->_data[$k]);
+                    break;
+                case 'enum':
+                    $this->_data[$k] = $this->_table->enumValue($k, $this->_data[$k]);
+                    break;
+
+            }
+        }
+
+        $this->_table->getRepository()->add($this);
+
+        $this->cleanData($this->_data);
+
+        $this->prepareIdentifiers($this->exists());
+
+        $this->postUnserialize($event);
+    }
+
+    public function state($state = null)
+    {
+        if ($state == null) {
+            return $this->_state;
+        }
+
+        $err = false;
+        if (is_integer($state)) {
+            if ($state >= 1 && $state <= 6) {
+                $this->_state = $state;
+            } else {
+                $err = true;
+            }
+        } else if (is_string($state)) {
+            $upper = strtoupper($state);
+
+            $const = 'IPF_ORM_Record::STATE_' . $upper;
+            if (defined($const)) {
+                $this->_state = constant($const);
+            } else {
+                $err = true;
+            }
+        }
+
+        if ($this->_state === IPF_ORM_Record::STATE_TCLEAN ||
+                $this->_state === IPF_ORM_Record::STATE_CLEAN) {
+            $this->_modified = array();
+        }
+
+        if ($err) {
+            throw new IPF_ORM_Exception('Unknown record state ' . $state);
+        }
+    }
+
+    public function refresh($deep = false)
+    {
+        $id = $this->identifier();
+        if ( ! is_array($id)) {
+            $id = array($id);
+        }
+        if (empty($id)) {
+            return false;
+        }
+        $id = array_values($id);
+
+        if ($deep) {
+            $query = $this->getTable()->createQuery();
+            foreach (array_keys($this->_references) as $name) {
+                $query->leftJoin(get_class($this) . '.' . $name);
+            }
+            $query->where(implode(' = ? AND ', $this->getTable()->getIdentifierColumnNames()) . ' = ?');
+            $this->clearRelated();
+            $record = $query->fetchOne($id);
+        } else {
+            // Use FETCH_ARRAY to avoid clearing object relations
+            $record = $this->getTable()->find($id, IPF_ORM::HYDRATE_ARRAY);
+            if ($record) {
+                $this->hydrate($record);
+            }
+        }
+
+        if ($record === false) {
+            throw new IPF_ORM_Exception('Failed to refresh. Record does not exist.');
+        }
+
+        $this->_modified = array();
+
+        $this->prepareIdentifiers();
+
+        $this->_state = IPF_ORM_Record::STATE_CLEAN;
+
+        return $this;
+    }
+
+    public function refreshRelated($name = null)
+    {
+        if (is_null($name)) {
+            foreach ($this->_table->getRelations() as $rel) {
+                $this->_references[$rel->getAlias()] = $rel->fetchRelatedFor($this);
+            }
+        } else {
+            $rel = $this->_table->getRelation($name);
+            $this->_references[$name] = $rel->fetchRelatedFor($this);
+        }
+    }
+
+    public function clearRelated()
+    {
+        $this->_references = array();
+    }
+
+    public function getTable()
+    {
+        return $this->_table;
+    }
+
+    public function getData()
+    {
+        return $this->_data;
+    }
+
+    public function rawGet($fieldName)
+    {
+        if ( ! isset($this->_data[$fieldName])) {
+            throw new IPF_ORM_Exception('Unknown property '. $fieldName);
+        }
+        if (IPF_ORM_Null::isNull($this->_data[$fieldName])) {
+            return null;
+        }
+
+        return $this->_data[$fieldName];
+    }
+
+    public function load()
+    {
+        // only load the data from database if the IPF_ORM_Record is in proxy state
+        if ($this->_state == IPF_ORM_Record::STATE_PROXY) {
+            $this->refresh();
+            $this->_state = IPF_ORM_Record::STATE_CLEAN;
+            return true;
+        }
+        return false;
+    }
+
+    public function get($fieldName, $load = true)
+    {
+        $value = IPF_ORM_Null::getInstance();
+
+        if (isset($this->_data[$fieldName])) {
+            if (IPF_ORM_Null::isNull($this->_data[$fieldName]) && $load) {
+                $this->load();
+            }
+            if (IPF_ORM_Null::isNull($this->_data[$fieldName])) {
+                $value = null;
+            } else {
+                $value = $this->_data[$fieldName];
+            }
+            return $value;
+        }
+
+        if (isset($this->_values[$fieldName])) {
+            return $this->_values[$fieldName];
+        }
+
+        if (!isset($this->_references[$fieldName]) && $load) {
+            $rel = $this->_table->getRelation($fieldName);
+            $this->_references[$fieldName] = $rel->fetchRelatedFor($this);
+        }
+        return $this->_references[$fieldName];
+    }
+
+    public function mapValue($name, $value)
+    {
+        $this->_values[$name] = $value;
+    }
+
+    public function set($fieldName, $value, $load = true)
+    {
+        if (isset($this->_data[$fieldName])) {
+            $type = $this->_table->getTypeOf($fieldName);
+            if ($value instanceof IPF_ORM_Record) {
+                $id = $value->getIncremented();
+
+                if ($id !== null && $type !== 'object') {
+                    $value = $id;
+                }
+            }
+
+            if ($load) {
+                $old = $this->get($fieldName, $load);
+            } else {
+                $old = $this->_data[$fieldName];
+            }
+
+            if ($this->_isValueModified($type, $old, $value)) {
+                if ($value === null) {
+                    $value = IPF_ORM_Null::getInstance();
+                }
+
+                $this->_data[$fieldName] = $value;
+                $this->_modified[] = $fieldName;
+                switch ($this->_state) {
+                    case IPF_ORM_Record::STATE_CLEAN:
+                        $this->_state = IPF_ORM_Record::STATE_DIRTY;
+                        break;
+                    case IPF_ORM_Record::STATE_TCLEAN:
+                        $this->_state = IPF_ORM_Record::STATE_TDIRTY;
+                        break;
+                }
+            }
+        } else {
+            $this->coreSetRelated($fieldName, $value);
+        }
+
+        return $this;
+    }
+
+    protected function _isValueModified($type, $old, $new)
+    {
+        if ($type == 'boolean' && (is_bool($old) || is_numeric($old)) && (is_bool($new) || is_numeric($new)) && $old == $new) {
+            return false;
+        } else {
+            return $old !== $new;
+        }
+    }
+
+    public function coreSetRelated($name, $value)
+    {
+        $rel = $this->_table->getRelation($name);
+
+        if ($value === null) {
+            $value = IPF_ORM_Null::getInstance();
+        }
+
+        // one-to-many or one-to-one relation
+        if ($rel instanceof IPF_ORM_Relation_ForeignKey || $rel instanceof IPF_ORM_Relation_LocalKey) {
+            if ( ! $rel->isOneToOne()) {
+                // one-to-many relation found
+                if ( ! ($value instanceof IPF_ORM_Collection)) {
+                    throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Collection when setting one-to-many references.");
+                }
+                if (isset($this->_references[$name])) {
+                    $this->_references[$name]->setData($value->getData());
+                    return $this;
+                }
+            } else {
+                if (!IPF_ORM_Null::isNull($value)) {
+                    $relatedTable = $value->getTable();
+                    $foreignFieldName = $relatedTable->getFieldName($rel->getForeign());
+                    $localFieldName = $this->_table->getFieldName($rel->getLocal());
+
+                    // one-to-one relation found
+                    if ( ! ($value instanceof IPF_ORM_Record)) {
+                        throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Record or IPF_ORM_Null when setting one-to-one references.");
+                    }
+                    if ($rel instanceof IPF_ORM_Relation_LocalKey) {
+                        if ( ! empty($foreignFieldName) && $foreignFieldName != $value->getTable()->getIdentifier()) {
+                            $this->set($localFieldName, $value->rawGet($foreignFieldName), false);
+                        } else {
+                            $this->set($localFieldName, $value, false);
+                        }
+                    } else {
+                        $value->set($foreignFieldName, $this, false);
+                    }
+                }
+            }
+
+        } else if ($rel instanceof IPF_ORM_Relation_Association) {
+            // join table relation found
+            if ( ! ($value instanceof IPF_ORM_Collection)) {
+                throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Collection when setting many-to-many references.");
+            }
+        }
+
+        $this->_references[$name] = $value;
+    }
+
+    public function contains($fieldName)
+    {
+        if (isset($this->_data[$fieldName])) {
+            // this also returns true if the field is a IPF_ORM_Null.
+            // imho this is not correct behavior.
+            return true;
+        }
+        if (isset($this->_id[$fieldName])) {
+            return true;
+        }
+        if (isset($this->_values[$fieldName])) {
+            return true;
+        }
+        if (isset($this->_references[$fieldName]) && !IPF_ORM_Null::isNull($this->_references[$fieldName])) {
+            return true;
+        }
+        return false;
+    }
+
+    public function __unset($name)
+    {
+        if (isset($this->_data[$name])) {
+            $this->_data[$name] = array();
+        } else if (isset($this->_references[$name])) {
+            if ($this->_references[$name] instanceof IPF_ORM_Record) {
+                $this->_pendingDeletes[] = $this->$name;
+                $this->_references[$name] = IPF_ORM_Null::getInstance();
+            } elseif ($this->_references[$name] instanceof IPF_ORM_Collection) {
+                $this->_pendingDeletes[] = $this->$name;
+                $this->_references[$name]->setData(array());
+            }
+        }
+    }
+
+    public function getPendingDeletes()
+    {
+        return $this->_pendingDeletes;
+    }
+
+    public function save(IPF_ORM_Connection $conn = null)
+    {
+        if ($conn === null) {
+            $conn = $this->_table->getConnection();
+        }
+        $conn->unitOfWork->saveGraph($this);
+    }
+
+    public function trySave(IPF_ORM_Connection $conn = null) {
+        try {
+            $this->save($conn);
+            return true;
+        } catch (IPF_ORM_Exception_Validator $ignored) {
+            return false;
+        }
+    }
+
+    public function replace(IPF_ORM_Connection $conn = null)
+    {
+        if ($conn === null) {
+            $conn = $this->_table->getConnection();
+        }
+
+        if ($this->exists()) {
+            return $this->save();
+        } else {
+            $identifier = (array) $this->getTable()->getIdentifier();
+            return $conn->replace($this->_table, $this->toArray(), $identifier);
+        }
+    }
+
+    public function getModified()
+    {
+        $a = array();
+
+        foreach ($this->_modified as $k => $v) {
+            $a[$v] = $this->_data[$v];
+        }
+        return $a;
+    }
+
+    public function modifiedFields()
+    {
+        $a = array();
+
+        foreach ($this->_modified as $k => $v) {
+            $a[$v] = $this->_data[$v];
+        }
+        return $a;
+    }
+
+    public function getPrepared(array $array = array())
+    {
+        $a = array();
+
+        if (empty($array)) {
+            $modifiedFields = $this->_modified;
+        }
+
+        foreach ($modifiedFields as $field) {
+            $type = $this->_table->getTypeOf($field);
+
+            if (IPF_ORM_Null::isNull($this->_data[$field])) {
+                $a[$field] = null;
+                continue;
+            }
+
+            switch ($type) {
+                case 'array':
+                case 'object':
+                    $a[$field] = serialize($this->_data[$field]);
+                    break;
+                case 'gzip':
+                    $a[$field] = gzcompress($this->_data[$field],5);
+                    break;
+                case 'boolean':
+                    $a[$field] = $this->getTable()->getConnection()->convertBooleans($this->_data[$field]);
+                break;
+                case 'enum':
+                    $a[$field] = $this->_table->enumIndex($field, $this->_data[$field]);
+                    break;
+                default:
+                    if ($this->_data[$field] instanceof IPF_ORM_Record) {
+                        $a[$field] = $this->_data[$field]->getIncremented();
+                        if ($a[$field] !== null) {
+                            $this->_data[$field] = $a[$field];
+                        }
+                    } else {
+                        $a[$field] = $this->_data[$field];
+                    }
+                    /** TODO:
+                    if ($this->_data[$v] === null) {
+                        throw new IPF_ORM_Record_Exception('Unexpected null value.');
+                    }
+                    */
+            }
+        }
+        $map = $this->_table->inheritanceMap;
+        foreach ($map as $k => $v) {
+            $k = $this->_table->getFieldName($k);
+            $old = $this->get($k, false);
+
+            if ((string) $old !== (string) $v || $old === null) {
+                $a[$k] = $v;
+                $this->_data[$k] = $v;
+            }
+        }
+
+        return $a;
+    }
+
+    public function count()
+    {
+        return count($this->_data);
+    }
+
+    public function columnCount()
+    {
+        return $this->count();
+    }
+
+    public function toArray($deep = true, $prefixKey = false)
+    {
+        if ($this->_state == self::STATE_LOCKED) {
+            return false;
+        }
+
+        $stateBeforeLock = $this->_state;
+        $this->_state = self::STATE_LOCKED;
+
+        $a = array();
+
+        foreach ($this as $column => $value) {
+            if (IPF_ORM_Null::isNull($value) || is_object($value)) {
+                $value = null;
+            }
+
+            $a[$column] = $value;
+        }
+
+        if ($this->_table->getIdentifierType() ==  IPF_ORM::IDENTIFIER_AUTOINC) {
+            $i      = $this->_table->getIdentifier();
+            $a[$i]  = $this->getIncremented();
+        }
+
+        if ($deep) {
+            foreach ($this->_references as $key => $relation) {
+                if (!IPF_ORM_Null::isNull($relation)) {
+                    $a[$key] = $relation->toArray($deep, $prefixKey);
+                }
+            }
+        }
+
+        // [FIX] Prevent mapped IPF_ORM_Records from being displayed fully
+        foreach ($this->_values as $key => $value) {
+            if ($value instanceof IPF_ORM_Record) {
+                $a[$key] = $value->toArray($deep, $prefixKey);
+            } else {
+                $a[$key] = $value;
+            }
+        }
+
+        $this->_state = $stateBeforeLock;
+
+        return $a;
+    }
+
+    public function merge($data, $deep = true)
+    {
+        if ($data instanceof $this) {
+            $array = $data->toArray($deep);
+        } else if (is_array($data)) {
+            $array = $data;
+        }
+
+        return $this->fromArray($array, $deep);
+    }
+
+    public function fromArray(array $array, $deep = true)
+    {
+        $refresh = false;
+        foreach ($array as $key => $value) {
+            if ($key == '_identifier') {
+                $refresh = true;
+                $this->assignIdentifier((array) $value);
+                continue;
+            }
+
+            if ($deep && $this->getTable()->hasRelation($key)) {
+                $this->$key->fromArray($value, $deep);
+            } else if ($this->getTable()->hasField($key)) {
+                $this->set($key, $value);
+            }
+        }
+
+        if ($refresh) {
+            $this->refresh();
+        }
+    }
+
+    protected function _synchronizeWithArrayForRelation($key, $value)
+    {
+        $this->get($key)->synchronizeWithArray($value);
+    }
+
+    public function synchronizeWithArray(array $array, $deep = true)
+    {
+        $refresh = false;
+        foreach ($array as $key => $value) {
+            if ($key == '_identifier') {
+                $refresh = true;
+                $this->assignIdentifier((array) $value);
+                continue;
+            }
+            if ($deep && $this->getTable()->hasRelation($key)) {
+                $this->_synchronizeWithArrayForRelation($key, $value);
+            } else if ($this->getTable()->hasField($key)) {
+                $this->set($key, $value);
+            }
+        }
+        // eliminate relationships missing in the $array
+        foreach ($this->_references as $name => $obj) {
+            if ( ! isset($array[$name])) {
+                unset($this->$name);
+            }
+        }
+        if ($refresh) {
+            $this->refresh();
+        }
+    }
+
+    public function exists()
+    {
+        return ($this->_state !== IPF_ORM_Record::STATE_TCLEAN &&
+                $this->_state !== IPF_ORM_Record::STATE_TDIRTY);
+    }
+
+    public function isModified()
+    {
+        return ($this->_state === IPF_ORM_Record::STATE_DIRTY ||
+                $this->_state === IPF_ORM_Record::STATE_TDIRTY);
+    }
+
+    public function hasRelation($fieldName)
+    {
+        if (isset($this->_data[$fieldName]) || isset($this->_id[$fieldName])) {
+            return true;
+        }
+        return $this->_table->hasRelation($fieldName);
+    }
+
+    public function getIterator()
+    {
+        return new IPF_ORM_Record_Iterator($this);
+    }
+
+    public function delete(IPF_ORM_Connection $conn = null)
+    {
+        if ($conn == null) {
+            $conn = $this->_table->getConnection();
+        }
+        return $conn->unitOfWork->delete($this);
+    }
+
+    public function copy($deep = false)
+    {
+        $data = $this->_data;
+
+        if ($this->_table->getIdentifierType() === IPF_ORM::IDENTIFIER_AUTOINC) {
+            $id = $this->_table->getIdentifier();
+
+            unset($data[$id]);
+        }
+
+        $ret = $this->_table->create($data);
+        $modified = array();
+
+        foreach ($data as $key => $val) {
+            if (!IPF_ORM_Null::isNull($val)) {
+                $ret->_modified[] = $key;
+            }
+        }
+
+        if ($deep) {
+            foreach ($this->_references as $key => $value) {
+                if ($value instanceof IPF_ORM_Collection) {
+                    foreach ($value as $record) {
+                        $ret->{$key}[] = $record->copy($deep);
+                    }
+                } else if($value instanceof IPF_ORM_Record) {
+                    $ret->set($key, $value->copy($deep));
+                }
+            }
+        }
+
+        return $ret;
+    }
+
+    public function assignIdentifier($id = false)
+    {
+        if ($id === false) {
+            $this->_id       = array();
+            $this->_data     = $this->cleanData($this->_data);
+            $this->_state    = IPF_ORM_Record::STATE_TCLEAN;
+            $this->_modified = array();
+        } elseif ($id === true) {
+            $this->prepareIdentifiers(true);
+            $this->_state    = IPF_ORM_Record::STATE_CLEAN;
+            $this->_modified = array();
+        } else {
+            if (is_array($id)) {
+                foreach ($id as $fieldName => $value) {
+                    $this->_id[$fieldName] = $value;
+                    $this->_data[$fieldName] = $value;
+                }
+            } else {
+                $name = $this->_table->getIdentifier();
+                $this->_id[$name] = $id;
+                $this->_data[$name] = $id;
+            }
+            $this->_state = IPF_ORM_Record::STATE_CLEAN;
+            $this->_modified = array();
+        }
+    }
+
+    public function identifier()
+    {
+        return $this->_id;
+    }
+
+    final public function getIncremented()
+    {
+        $id = current($this->_id);
+        if ($id === false) {
+            return null;
+        }
+
+        return $id;
+    }
+
+    public function pk($sep='_')
+    {
+        $pk = '';
+        foreach($this->_id as $val) {
+            if ($pk!='')
+                $pk .= $sep;
+            $pk .= $val;
+        }
+        return $pk;
+    }
+
+    public function getLast()
+    {
+        return $this;
+    }
+
+    public function hasReference($name)
+    {
+        return isset($this->_references[$name]);
+    }
+
+    public function reference($name)
+    {
+        if (isset($this->_references[$name])) {
+            return $this->_references[$name];
+        }
+    }
+
+    public function obtainReference($name)
+    {
+        if (isset($this->_references[$name])) {
+            return $this->_references[$name];
+        }
+        throw new IPF_ORM_Exception("Unknown reference $name");
+    }
+
+    public function getReferences()
+    {
+        return $this->_references;
+    }
+
+    final public function setRelated($alias, IPF_ORM_Access $coll)
+    {
+        $this->_references[$alias] = $coll;
+    }
+
+    public function loadReference($name)
+    {
+        $rel = $this->_table->getRelation($name);
+        $this->_references[$name] = $rel->fetchRelatedFor($this);
+    }
+
+    public function call($callback, $column)
+    {
+        $args = func_get_args();
+        array_shift($args);
+
+        if (isset($args[0])) {
+            $fieldName = $args[0];
+            $args[0] = $this->get($fieldName);
+
+            $newvalue = call_user_func_array($callback, $args);
+
+            $this->_data[$fieldName] = $newvalue;
+        }
+        return $this;
+    }
+
+    public function unlink($alias, $ids = array())
+    {
+        $ids = (array) $ids;
+
+        $q = new IPF_ORM_Query();
+
+        $rel = $this->getTable()->getRelation($alias);
+
+        if ($rel instanceof IPF_ORM_Relation_Association) {
+            $q->delete()
+              ->from($rel->getAssociationTable()->getComponentName())
+              ->where($rel->getLocal() . ' = ?', array_values($this->identifier()));
+
+            if (count($ids) > 0) {
+                $q->whereIn($rel->getForeign(), $ids);
+            }
+
+            $q->execute();
+
+        } else if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
+            $q->update($rel->getTable()->getComponentName())
+              ->set($rel->getForeign(), '?', array(null))
+              ->addWhere($rel->getForeign() . ' = ?', array_values($this->identifier()));
+
+            if (count($ids) > 0) {
+                $q->whereIn($rel->getTable()->getIdentifier(), $ids);
+            }
+
+            $q->execute();
+        }
+        if (isset($this->_references[$alias])) {
+            foreach ($this->_references[$alias] as $k => $record) {
+                if (in_array(current($record->identifier()), $ids)) {
+                    $this->_references[$alias]->remove($k);
+                }
+            }
+            $this->_references[$alias]->takeSnapshot();
+        }
+        return $this;
+    }
+
+    public function link($alias, $ids)
+    {
+        $ids = (array) $ids;
+
+        if ( ! count($ids)) {
+            return $this;
+        }
+
+        $identifier = array_values($this->identifier());
+        $identifier = array_shift($identifier);
+
+        $rel = $this->getTable()->getRelation($alias);
+
+        if ($rel instanceof IPF_ORM_Relation_Association) {
+
+            $modelClassName = $rel->getAssociationTable()->getComponentName();
+            $localFieldName = $rel->getLocalFieldName();
+            $localFieldDef  = $rel->getAssociationTable()->getColumnDefinition($localFieldName);
+            if ($localFieldDef['type'] == 'integer') {
+                $identifier = (integer) $identifier;
+            }
+            $foreignFieldName = $rel->getForeignFieldName();
+            $foreignFieldDef  = $rel->getAssociationTable()->getColumnDefinition($foreignFieldName);
+            if ($foreignFieldDef['type'] == 'integer') {
+                for ($i = 0; $i < count($ids); $i++) {
+                    $ids[$i] = (integer) $ids[$i];
+                }
+            }
+
+            foreach ($ids as $id) {
+                $record = new $modelClassName;
+                $record[$localFieldName]   = $identifier;
+                $record[$foreignFieldName] = $id;
+                $record->save();
+            }
+
+        } else if ($rel instanceof IPF_ORM_Relation_ForeignKey) {
+
+            $q = new IPF_ORM_Query();
+
+            $q->update($rel->getTable()->getComponentName())
+              ->set($rel->getForeign(), '?', array_values($this->identifier()));
+
+            if (count($ids) > 0) {
+                $q->whereIn($rel->getTable()->getIdentifier(), $ids);
+            }
+
+            $q->execute();
+
+        } else if ($rel instanceof IPF_ORM_Relation_LocalKey) {
+
+            $q = new IPF_ORM_Query();
+
+            $q->update($this->getTable()->getComponentName())
+              ->set($rel->getLocalFieldName(), '?', $ids);
+
+            if (count($ids) > 0) {
+                $q->whereIn($rel->getTable()->getIdentifier(), array_values($this->identifier()));
+            }
+
+            $q->execute();
+
+        }
+
+        return $this;
+    }
+
+    public function __call($method, $args)
+    {
+        if (($template = $this->_table->getMethodOwner($method)) !== false) {
+            $template->setInvoker($this);
+            return call_user_func_array(array($template, $method), $args);
+        }
+
+        foreach ($this->_table->getTemplates() as $template) {
+            if (method_exists($template, $method)) {
+                $template->setInvoker($this);
+                $this->_table->setMethodOwner($method, $template);
+
+                return call_user_func_array(array($template, $method), $args);
+            }
+        }
+
+        throw new IPF_ORM_Exception(sprintf('Unknown method %s::%s', get_class($this), $method));
+    }
+
+    public function free($deep = false)
+    {
+        if ($this->_state != self::STATE_LOCKED) {
+            $this->_state = self::STATE_LOCKED;
+
+            $this->_table->getRepository()->evict($this->_oid);
+            $this->_table->removeRecord($this);
+            $this->_data = array();
+            $this->_id = array();
+
+            if ($deep) {
+                foreach ($this->_references as $name => $reference) {
+                    if (!IPF_ORM_Null::isNull($reference)) {
+                        $reference->free($deep);
+                    }
+                }
+            }
+
+            $this->_references = array();
+        }
+    }
+
+    public function toString()
+    {
+        return IPF_ORM::dump(get_object_vars($this));
+    }
+
+    public function __toString()
+    {
+        return sprintf('<%s #%d>', get_class($this), $this->_oid);
+    }
+
+    public function SetFromFormData($cleaned_values)
+    {
+        $names = $this->_table->getFieldNames();
+        foreach ($cleaned_values as $key=>$val) {
+            $validators = $this->getTable()->getFieldValidators($key);
+            if (
+                array_key_exists('image',$validators) ||
+                array_key_exists('file',$validators) ||
+                array_key_exists('email',$validators)
+            ){
+                if (($val!==null) && ($val==''))
+                    continue;
+            }
+            if (array_search($key,$names)){
+                $this->$key = $val;
+            }
+        }
+    }
+
+    public function SetCustom($name, $val)
+    {
+        $this->_custom[$name] = $val;
+    }
+
+    public function GetCustom($name)
+    {
+        if (isset($this->_custom[$name]))
+            return $this->_custom[$name];
+        return null;
+    }
+}
+
diff --git a/src/orm/record/abstract.php b/src/orm/record/abstract.php
new file mode 100644 (file)
index 0000000..07aab0d
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+
+abstract class IPF_ORM_Record_Abstract extends IPF_ORM_Access
+{
+    protected $_table;
+
+    public static function setTableDefinition(IPF_ORM_Table $table)
+    {
+    }
+
+    public static function setUp(IPF_ORM_Table $table)
+    {
+    }
+
+    public function getTable()
+    {
+        return $this->_table;
+    }
+
+    public function setInheritanceMap($map)
+    {
+        $this->_table->setOption('inheritanceMap', $map);
+    }
+
+    public function attribute($attr, $value)
+    {
+        if ($value == null) {
+            if (is_array($attr)) {
+                foreach ($attr as $k => $v) {
+                    $this->_table->setAttribute($k, $v);
+                }
+            } else {
+                return $this->_table->getAttribute($attr);
+            }
+        } else {
+            $this->_table->setAttribute($attr, $value);
+        }    
+    }
+
+    public function check($constraint, $name = null)
+    {
+        if (is_array($constraint)) {
+            foreach ($constraint as $name => $def) {
+                $this->_table->addCheckConstraint($def, $name);
+            }
+        } else {
+            $this->_table->addCheckConstraint($constraint, $name);
+        }
+        return $this;
+    }
+
+    public function __debugInfo()
+    {
+        $r = array();
+        foreach ($this->getTable()->getColumnNames() as $column)
+            $r[$column] = $this->get($column);
+        return $r;
+    }
+}
+
diff --git a/src/orm/record/iterator.php b/src/orm/record/iterator.php
new file mode 100644 (file)
index 0000000..505a990
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+class IPF_ORM_Record_Iterator extends ArrayIterator
+{
+    private $record;
+
+    public function __construct(IPF_ORM_Record $record)
+    {
+        $this->record = $record;
+        parent::__construct($record->getData());
+    }
+
+    public function current()
+    {
+        $value = parent::current();
+
+        if (IPF_ORM_Null::isNull($value)) {
+            return null;
+        } else {
+            return $value;
+        }
+    }
+}
+
diff --git a/src/orm/relation.php b/src/orm/relation.php
new file mode 100644 (file)
index 0000000..3d0c550
--- /dev/null
@@ -0,0 +1,175 @@
+<?php
+
+abstract class IPF_ORM_Relation implements ArrayAccess
+{
+    const ONE_AGGREGATE         = 0;
+    const ONE_COMPOSITE         = 1;
+    const MANY_AGGREGATE        = 2;
+    const MANY_COMPOSITE        = 3;
+
+    const ONE   = 0;
+    const MANY  = 2;
+
+    protected $definition = array('alias'       => true,
+                                  'foreign'     => true,
+                                  'local'       => true,
+                                  'class'       => true,
+                                  'type'        => true,
+                                  'table'       => true,
+                                  'localTable'  => true,
+                                  'name'        => null,
+                                  'refTable'    => null,
+                                  'onDelete'    => null,
+                                  'onUpdate'    => null,
+                                  'deferred'    => null,
+                                  'deferrable'  => null,
+                                  'constraint'  => null,
+                                  'equal'       => false,
+                                  'cascade'     => array(), // application-level cascades
+                                  'owningSide'  => false, // whether this is the owning side
+                                  'exclude'     => false,
+                                  );
+
+    public function __construct(array $definition)
+    {
+        $def = array();
+        foreach ($this->definition as $key => $val) {
+            if ( ! isset($definition[$key]) && $val) {
+                throw new IPF_ORM_Exception($key . ' is required!');
+            }
+            if (isset($definition[$key])) {
+                $def[$key] = $definition[$key];
+            } else {
+                $def[$key] = $this->definition[$key];
+            }
+        }
+        $this->definition = $def;
+    }
+
+    public function hasConstraint()
+    {
+        return $this->definition['constraint'] ||
+               $this->definition['onUpdate'] ||
+               $this->definition['onDelete'];
+    }
+
+    public function isDeferred()
+    {
+        return $this->definition['deferred'];
+    }
+
+    public function isDeferrable()
+    {
+        return $this->definition['deferrable'];
+    }
+    public function isEqual()
+    {
+        return $this->definition['equal'];
+    }
+
+    public function offsetExists($offset)
+    {
+        return isset($this->definition[$offset]);
+    }
+
+    public function offsetGet($offset)
+    {
+        if (isset($this->definition[$offset])) {
+            return $this->definition[$offset];
+        }
+
+        return null;
+    }
+
+    public function offsetSet($offset, $value)
+    {
+        if (isset($this->definition[$offset])) {
+            $this->definition[$offset] = $value;
+        }
+    }
+
+    public function offsetUnset($offset)
+    {
+        $this->definition[$offset] = false;
+    }
+
+    public function toArray()
+    {
+        return $this->definition;
+    }
+
+    final public function getAlias()
+    {
+        return $this->definition['alias'];
+    }
+
+    final public function getType()
+    {
+        return $this->definition['type'];
+    }
+
+    public function isCascadeDelete()
+    {
+        return in_array('delete', $this->definition['cascade']);
+    }
+
+    final public function getTable()
+    {
+        return IPF_ORM_Manager::connection()->getTable($this->definition['class']);
+    }
+
+    final public function getClass()
+    {
+        return $this->definition['class'];
+    }
+
+    final public function getLocal()
+    {
+        return $this->definition['local'];
+    }
+
+    final public function getLocalFieldName()
+    {
+        return $this->definition['localTable']->getFieldName($this->definition['local']);
+    }
+
+    final public function getForeign()
+    {
+        return $this->definition['foreign'];
+    }
+
+    final public function getForeignFieldName()
+    {
+        return $this->definition['table']->getFieldName($this->definition['foreign']);
+    }
+
+    final public function isComposite()
+    {
+        return ($this->definition['type'] == IPF_ORM_Relation::ONE_COMPOSITE ||
+                $this->definition['type'] == IPF_ORM_Relation::MANY_COMPOSITE);
+    }
+
+    final public function isOneToOne()
+    {
+        return ($this->definition['type'] == IPF_ORM_Relation::ONE_AGGREGATE ||
+                $this->definition['type'] == IPF_ORM_Relation::ONE_COMPOSITE);
+    }
+
+    abstract public function getRelationDql($count, $context);
+
+    abstract public function fetchRelatedFor(IPF_ORM_Record $record);
+
+    public function __toString()
+    {
+        $r[] = "<pre>";
+        foreach ($this->definition as $k => $v) {
+            if (is_object($v)) {
+                $v = 'Object(' . get_class($v) . ')';
+            }
+            $r[] = $k . ' : ' . $v;
+        }
+        $r[] = "</pre>";
+        return implode("\n", $r);
+    }
+}
+
diff --git a/src/orm/relation/association.php b/src/orm/relation/association.php
new file mode 100644 (file)
index 0000000..29fc2b2
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+
+class IPF_ORM_Relation_Association extends IPF_ORM_Relation
+{
+    public function getAssociationFactory()
+    {
+        return $this->definition['refTable'];
+    }
+    public function getAssociationTable()
+    {
+        return $this->definition['refTable'];
+    }
+
+    public function getRelationDql($count, $context)
+    {
+        $component = $this->definition['refTable']->getComponentName();
+        $thisTable = $this->getTable()->getComponentName();
+
+        $sub = substr(str_repeat('?, ', $count), 0, -2);
+        switch ($context) {
+            case "record":
+                $dql = 'FROM ' . $thisTable . '.' . $component .
+                      ' WHERE ' . $thisTable . '.' . $component . '.' . $this->definition['local'] . ' IN (' . $sub . ')';
+                break;
+            case "collection":
+                $dql = 'FROM ' . $component . '.' . $thisTable .
+                      ' WHERE ' . $component . '.' . $this->definition['local'] . ' IN (' . $sub . ')';
+                break;
+        }
+        return $dql;
+    }
+
+    public function fetchRelatedFor(IPF_ORM_Record $record)
+    {
+        $id = $record->pk();
+        if (empty($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
+            $coll = new IPF_ORM_Collection($this->getTable());
+        } else {
+            $coll = $this->getTable()->getConnection()->query($this->getRelationDql(1, 'record'), array($id));
+        }
+        $coll->setReference($record, $this);
+        return $coll;
+    }
+}
+
diff --git a/src/orm/relation/foreignkey.php b/src/orm/relation/foreignkey.php
new file mode 100644 (file)
index 0000000..5e5a91f
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+class IPF_ORM_Relation_ForeignKey extends IPF_ORM_Relation
+{
+    public function fetchRelatedFor(IPF_ORM_Record $record)
+    {
+        $id = array();
+        $localTable = $record->getTable();
+        foreach ((array) $this->definition['local'] as $local) {
+           $value = $record->get($localTable->getFieldName($local));
+           if (isset($value)) {
+               $id[] = $value;
+           }
+        }
+        if ($this->isOneToOne()) {
+            if ( ! $record->exists() || empty($id) || 
+                 ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
+                
+                $related = $this->getTable()->create();
+            } else {
+                $dql  = 'FROM ' . $this->getTable()->getComponentName()
+                      . ' WHERE ' . $this->getCondition();
+
+                $coll = $this->getTable()->getConnection()->query($dql, $id);
+                $related = $coll[0];
+            }
+
+            $related->set($related->getTable()->getFieldName($this->definition['foreign']),
+                    $record, false);
+        } else {
+
+            if ( ! $record->exists() || empty($id) || 
+                 ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
+                
+                $related = new IPF_ORM_Collection($this->getTable());
+            } else {
+                $query      = $this->getRelationDql(1, '');
+                $related    = $this->getTable()->getConnection()->query($query, $id);
+            }
+            $related->setReference($record, $this);
+        }
+        return $related;
+    }
+
+    public function getRelationDql($count, $context)
+    {
+        $table = $this->getTable();
+        $component = $table->getComponentName();
+
+        $dql  = 'FROM ' . $component
+              . ' WHERE ' . $component . '.' . $this->definition['foreign']
+              . ' IN (' . substr(str_repeat('?, ', $count), 0, -2) . ')';
+
+        $ordering = $table->getOrdering();
+        if ($ordering)
+            $dql .= ' ORDER BY ' . implode(', ', $ordering);
+
+        return $dql;
+    }
+
+    public function getCondition($alias = null)
+    {
+        if ( ! $alias) {
+           $alias = $this->getTable()->getComponentName();
+        }
+        $conditions = array();
+        foreach ((array) $this->definition['foreign'] as $foreign) {
+            $conditions[] = $alias . '.' . $foreign . ' = ?';
+        }
+        return implode(' AND ', $conditions);
+    }
+}
\ No newline at end of file
diff --git a/src/orm/relation/localkey.php b/src/orm/relation/localkey.php
new file mode 100644 (file)
index 0000000..edd225f
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+
+class IPF_ORM_Relation_LocalKey extends IPF_ORM_Relation
+{
+    public function fetchRelatedFor(IPF_ORM_Record $record)
+    {
+        $localFieldName = $record->getTable()->getFieldName($this->definition['local']);
+        $id = $record->get($localFieldName);
+
+        if (is_null($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
+            $related = $this->getTable()->create();
+        } else {
+            $dql  = 'FROM ' . $this->getTable()->getComponentName()
+                 . ' WHERE ' . $this->getCondition();
+
+            $related = $this->getTable()
+                            ->getConnection()
+                            ->query($dql, array($id))
+                            ->getFirst();
+            
+            if ( ! $related || empty($related)) {
+                $related = $this->getTable()->create();
+            }
+        }
+
+        $record->set($localFieldName, $related, false);
+
+        return $related;
+    }
+
+    public function getRelationDql($count, $context)
+    {
+        $component = $this->getTable()->getComponentName();
+        return 'FROM ' . $component .
+              ' WHERE ' . $component . '.' . $this->definition['foreign'] .
+              ' IN (' . substr(str_repeat('?, ', $count), 0, -2) . ')';
+    }
+
+    public function getCondition($alias = null)
+    {
+        if ( ! $alias) {
+           $alias = $this->getTable()->getComponentName();
+        }
+        return $alias . '.' . $this->definition['foreign'] . ' = ?';
+    }
+}
+
diff --git a/src/orm/relation/nest.php b/src/orm/relation/nest.php
new file mode 100644 (file)
index 0000000..9e27245
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+
+class IPF_ORM_Relation_Nest extends IPF_ORM_Relation_Association
+{
+    public function getRelationDql($count, $context = 'record')
+    {
+        switch ($context) {
+            case 'record':
+                $identifierColumnNames = $this->definition['table']->getIdentifierColumnNames();
+                $identifier = array_pop($identifierColumnNames);
+                $sub    = 'SELECT '.$this->definition['foreign'] 
+                        . ' FROM '.$this->definition['refTable']->getTableName()
+                        . ' WHERE '.$this->definition['local']
+                        . ' = ?';
+
+                $sub2   = 'SELECT '.$this->definition['local']
+                        . ' FROM '.$this->definition['refTable']->getTableName()
+                        . ' WHERE '.$this->definition['foreign']
+                        . ' = ?';
+
+                $dql  = 'FROM ' . $this->definition['table']->getComponentName()
+                      . '.' . $this->definition['refTable']->getComponentName()
+                      . ' WHERE ' . $this->definition['table']->getComponentName()
+                      . '.' . $identifier 
+                      . ' IN (' . $sub . ')'
+                      . ' || ' . $this->definition['table']->getComponentName() 
+                      . '.' . $identifier
+                      . ' IN (' . $sub2 . ')';
+                break;
+            case 'collection':
+                $sub  = substr(str_repeat('?, ', $count),0,-2);
+                $dql  = 'FROM '.$this->definition['refTable']->getComponentName()
+                      . '.' . $this->definition['table']->getComponentName()
+                      . ' WHERE '.$this->definition['refTable']->getComponentName()
+                      . '.' . $this->definition['local'] . ' IN (' . $sub . ')';
+        };
+
+        return $dql;
+    }
+
+    public function fetchRelatedFor(IPF_ORM_Record $record)
+    {
+        $id = $record->getIncremented();
+
+
+        if (empty($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) {
+            return new IPF_ORM_Collection($this->getTable());
+        } else {
+            $q = new IPF_ORM_RawSql($this->getTable()->getConnection());
+
+            $assocTable = $this->getAssociationFactory()->getTableName();
+            $tableName  = $record->getTable()->getTableName();
+            $identifierColumnNames = $record->getTable()->getIdentifierColumnNames();
+            $identifier = array_pop($identifierColumnNames);
+    
+            $sub = 'SELECT ' . $this->getForeign()
+                 . ' FROM ' . $assocTable 
+                 . ' WHERE ' . $this->getLocal() 
+                 . ' = ?';
+
+            $condition[] = $tableName . '.' . $identifier . ' IN (' . $sub . ')';
+            $joinCondition[] = $tableName . '.' . $identifier . ' = ' . $assocTable . '.' . $this->getForeign();
+
+            if ($this->definition['equal']) {
+                $sub2   = 'SELECT ' . $this->getLocal()
+                        . ' FROM '  . $assocTable
+                        . ' WHERE ' . $this->getForeign()
+                        . ' = ?';
+
+                $condition[] = $tableName . '.' . $identifier . ' IN (' . $sub2 . ')';
+                $joinCondition[] = $tableName . '.' . $identifier . ' = ' . $assocTable . '.' . $this->getLocal();
+            }
+            $q->select('{'.$tableName.'.*}')
+              ->addSelect('{'.$assocTable.'.*}')
+              ->from($tableName . ' INNER JOIN ' . $assocTable . ' ON ' . implode(' OR ', $joinCondition))
+              ->where(implode(' OR ', $condition));
+            $q->addComponent($tableName,  $record->getTable()->getComponentName());
+            $q->addComponent($assocTable, $record->getTable()->getComponentName(). '.' . $this->getAssociationFactory()->getComponentName());
+
+            $params = ($this->definition['equal']) ? array($id, $id) : array($id);
+
+            return $q->execute($params);
+        }
+    }
+}
diff --git a/src/orm/relation/parser.php b/src/orm/relation/parser.php
new file mode 100644 (file)
index 0000000..ce1fe89
--- /dev/null
@@ -0,0 +1,362 @@
+<?php
+
+class IPF_ORM_Relation_Parser
+{
+    protected $_table;
+    protected $_relations = array();
+    protected $_pending   = array();
+
+    public function __construct(IPF_ORM_Table $table)
+    {
+        $this->_table = $table;
+    }
+
+    public function getTable()
+    {
+        return $this->_table;
+    }
+
+    public function getPendingRelation($name)
+    {
+        if ( ! isset($this->_pending[$name])) {
+            throw new IPF_ORM_Exception('Unknown pending relation ' . $name);
+        }
+
+        return $this->_pending[$name];
+    }
+
+    public function getPendingRelations()
+    {
+        return $this->_pending;
+    }
+
+    public function unsetPendingRelations($name)
+    {
+       unset($this->_pending[$name]);
+    }
+
+    public function hasRelation($name)
+    {
+        return isset($this->_pending[$name]) || isset($this->_relations[$name]);
+    }
+
+    public function bind($name, $alias, $type, $options=array())
+    {
+        if (!$alias)
+            $alias = $name;
+
+        unset($this->relations[$alias]);
+
+        $options['class'] = $name;
+        $options['alias'] = $alias;
+        $options['type']  = $type;
+
+        $this->_pending[$alias] = $options;
+    }
+
+    private function createRelation($alias)
+    {
+        if (isset($this->_relations[$alias]))
+            return;
+
+        if (!isset($this->_pending[$alias]))
+            throw new IPF_ORM_Exception('Unknown relation alias "' . $alias . '".');
+
+        $def = $this->_pending[$alias];
+        $identifierColumnNames = $this->_table->getIdentifierColumnNames();
+        $idColumnName = array_pop($identifierColumnNames);
+
+        // check if reference class name exists
+        // if it does we are dealing with association relation
+        if (isset($def['refClass'])) {
+            $def = $this->completeAssocDefinition($def);
+            $localClasses = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName()));
+
+            if ( ! isset($this->_pending[$def['refClass']]) &&
+                 ! isset($this->_relations[$def['refClass']])) {
+
+                $parser = $def['refTable']->getRelationParser();
+                if ( ! $parser->hasRelation($this->_table->getComponentName())) {
+                    $parser->bind($this->_table->getComponentName(), null, IPF_ORM_Relation::ONE, array(
+                        'local'     => $def['local'],
+                        'foreign'   => $idColumnName,
+                        'localKey'  => true,
+                    ));
+                }
+
+                if (!$this->hasRelation($def['refClass'])) {
+                    $this->bind($def['refClass'], null, IPF_ORM_Relation::MANY, array(
+                        'foreign' => $def['local'],
+                        'local'   => $idColumnName,
+                    ));
+                }
+            }
+            if (in_array($def['class'], $localClasses)) {
+                $rel = new IPF_ORM_Relation_Nest($def);
+            } else {
+                $rel = new IPF_ORM_Relation_Association($def);
+            }
+        } else {
+            // simple foreign key relation
+            $def = $this->completeDefinition($def);
+
+            if (isset($def['localKey'])) {
+                $rel = new IPF_ORM_Relation_LocalKey($def);
+
+                // Automatically index foreign keys which are not primary
+                $foreign = (array) $def['foreign'];
+                foreach ($foreign as $fk) {
+                    if ( ! $rel->getTable()->isIdentifier($fk)) {
+                        $rel->getTable()->addIndex($fk, array('fields' => array($fk)));
+                    }
+                }
+            } else {
+                $rel = new IPF_ORM_Relation_ForeignKey($def);
+            }
+        }
+
+        if (isset($rel)) {
+            // unset pending relation
+            unset($this->_pending[$alias]);
+
+            $this->_relations[$alias] = $rel;
+        }
+    }
+
+    public function getRelation($alias)
+    {
+        $this->getRelations();
+        return $this->_relations[$alias];
+    }
+
+    public function getRelations()
+    {
+        foreach ($this->_pending as $k => $v) {
+            $this->createRelation($k);
+        }
+
+        return $this->_relations;
+    }
+
+    public function completeAssocDefinition($def)
+    {
+        $conn = $this->_table->getConnection();
+
+        $def['table']       = IPF_ORM::getTable($def['class']);
+        $def['localTable']  = $this->_table;
+        $def['class']       = $def['table']->getComponentName();
+        $def['refTable']    = IPF_ORM::getTable($def['refClass']);
+
+        $id = $def['refTable']->getIdentifierColumnNames();
+
+        if (count($id) > 1) {
+            if ( ! isset($def['foreign'])) {
+                // foreign key not set
+                // try to guess the foreign key
+
+                $def['foreign'] = ($def['local'] === $id[0]) ? $id[1] : $id[0];
+            }
+            if ( ! isset($def['local'])) {
+                // foreign key not set
+                // try to guess the foreign key
+
+                $def['local'] = ($def['foreign'] === $id[0]) ? $id[1] : $id[0];
+            }
+        } else {
+
+            if ( ! isset($def['foreign'])) {
+                // foreign key not set
+                // try to guess the foreign key
+
+                $columns = $this->getIdentifiers($def['table']);
+
+                $def['foreign'] = $columns;
+            }
+            if ( ! isset($def['local'])) {
+                // local key not set
+                // try to guess the local key
+                $columns = $this->getIdentifiers($this->_table);
+
+                $def['local'] = $columns;
+            }
+        }
+        return $def;
+    }
+
+    public function getIdentifiers(IPF_ORM_Table $table)
+    {
+        $componentNameToLower = strtolower($table->getComponentName());
+        if (is_array($table->getIdentifier())) {
+            $columns = array();
+            foreach ((array) $table->getIdentifierColumnNames() as $identColName) {
+                $columns[] = $componentNameToLower . '_' . $identColName;
+            }
+        } else {
+            $columns = $componentNameToLower . '_' . $table->getColumnName(
+                    $table->getIdentifier());
+        }
+
+        return $columns;
+    }
+
+    public function guessColumns(array $classes, IPF_ORM_Table $foreignTable)
+    {
+        $conn = $this->_table->getConnection();
+
+        foreach ($classes as $class) {
+            try {
+                $table   = $conn->getTable($class);
+            } catch (IPF_ORM_Exception $e) {
+                continue;
+            }
+            $columns = $this->getIdentifiers($table);
+            $found   = true;
+
+            foreach ((array) $columns as $column) {
+                if ( ! $foreignTable->hasColumn($column)) {
+                    $found = false;
+                    break;
+                }
+            }
+            if ($found) {
+                break;
+            }
+        }
+
+        if ( ! $found) {
+            throw new IPF_ORM_Exception("Couldn't find columns.");
+        }
+
+        return $columns;
+    }
+
+    public function completeDefinition($def)
+    {
+        $conn = $this->_table->getConnection();
+        $def['table']       = IPF_ORM::getTable($def['class']);
+        $def['localTable']  = $this->_table;
+        $def['class']       = $def['table']->getComponentName();
+
+        $foreignClasses = array_merge($def['table']->getOption('parents'), array($def['class']));
+        $localClasses   = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName()));
+
+        $localIdentifierColumnNames = $this->_table->getIdentifierColumnNames();
+        $localIdentifierCount = count($localIdentifierColumnNames);
+        $localIdColumnName = array_pop($localIdentifierColumnNames);
+        $foreignIdentifierColumnNames = $def['table']->getIdentifierColumnNames();
+        $foreignIdColumnName = array_pop($foreignIdentifierColumnNames);
+
+        if (isset($def['local'])) {
+            if ( ! isset($def['foreign'])) {
+                // local key is set, but foreign key is not
+                // try to guess the foreign key
+
+                if ($def['local'] === $localIdColumnName) {
+                    $def['foreign'] = $this->guessColumns($localClasses, $def['table']);
+                } else {
+                    // the foreign field is likely to be the
+                    // identifier of the foreign class
+                    $def['foreign'] = $foreignIdColumnName;
+                    $def['localKey'] = true;
+                }
+            } else {
+                if ($localIdentifierCount == 1) {
+                    if ($def['local'] == $localIdColumnName && isset($def['owningSide'])
+                            && $def['owningSide'] === true) {
+                        $def['localKey'] = true;
+                    } else if (($def['local'] !== $localIdColumnName && $def['type'] == IPF_ORM_Relation::ONE)) {
+                        $def['localKey'] = true;
+                    }
+                } else if ($localIdentifierCount > 1) {
+                    // It's a composite key and since 'foreign' can not point to a composite
+                    // key currently, we know that 'local' must be the foreign key.
+                    $def['localKey'] = true;
+                }
+            }
+        } else {
+            if (isset($def['foreign'])) {
+                // local key not set, but foreign key is set
+                // try to guess the local key
+                if ($def['foreign'] === $foreignIdColumnName) {
+                    $def['localKey'] = true;
+                    try {
+                        $def['local'] = $this->guessColumns($foreignClasses, $this->_table);
+                    } catch (IPF_ORM_Exception $e) {
+                        $def['local'] = $localIdColumnName;
+                    }
+                } else {
+                    $def['local'] = $localIdColumnName;
+                }
+            } else {
+                // neither local or foreign key is being set
+                // try to guess both keys
+
+                $conn = $this->_table->getConnection();
+
+                // the following loops are needed for covering inheritance
+                foreach ($localClasses as $class) {
+                    $table = $conn->getTable($class);
+                    $identifierColumnNames = $table->getIdentifierColumnNames();
+                    $idColumnName = array_pop($identifierColumnNames);
+                    $column = strtolower($table->getComponentName())
+                            . '_' . $idColumnName;
+
+                    foreach ($foreignClasses as $class2) {
+                        $table2 = $conn->getTable($class2);
+                        if ($table2->hasColumn($column)) {
+                            $def['foreign'] = $column;
+                            $def['local'] = $idColumnName;
+                            return $def;
+                        }
+                    }
+                }
+
+                foreach ($foreignClasses as $class) {
+                    $table  = $conn->getTable($class);
+                    $identifierColumnNames = $table->getIdentifierColumnNames();
+                    $idColumnName = array_pop($identifierColumnNames);
+                    $column = strtolower($table->getComponentName())
+                            . '_' . $idColumnName;
+
+                    foreach ($localClasses as $class2) {
+                        $table2 = $conn->getTable($class2);
+                        if ($table2->hasColumn($column)) {
+                            $def['foreign']  = $idColumnName;
+                            $def['local']    = $column;
+                            $def['localKey'] = true;
+                            return $def;
+                        }
+                    }
+                }
+
+                // auto-add columns and auto-build relation
+                $columns = array();
+                foreach ((array) $this->_table->getIdentifierColumnNames() as $id) {
+                    // ?? should this not be $this->_table->getComponentName() ??
+                    $column = strtolower($table->getComponentName())
+                            . '_' . $id;
+
+                    $col = $this->_table->getColumnDefinition($id);
+                    $type = $col['type'];
+                    $length = $col['length'];
+
+                    unset($col['type']);
+                    unset($col['length']);
+                    unset($col['autoincrement']);
+                    unset($col['primary']);
+
+                    $def['table']->setColumn($column, $type, $length, $col);
+
+                    $columns[] = $column;
+                }
+                if (count($columns) > 1) {
+                    $def['foreign'] = $columns;
+                } else {
+                    $def['foreign'] = $columns[0];
+                }
+                $def['local'] = $localIdColumnName;
+            }
+        }
+        return $def;
+    }
+}
diff --git a/src/orm/table.php b/src/orm/table.php
new file mode 100644 (file)
index 0000000..6208103
--- /dev/null
@@ -0,0 +1,1033 @@
+<?php
+
+class IPF_ORM_Table extends IPF_ORM_Configurable implements Countable
+{
+    protected $_data             = array();
+    protected $_identifier = array();
+    protected $_identifierType;
+    protected $_conn;
+    protected $_identityMap        = array();
+
+    protected $_repository;
+    protected $_columns          = array();
+    protected $_fieldNames    = array();
+
+    protected $_columnNames = array();
+
+    protected $hasDefaultValues;
+
+    protected $_options      = array('name'           => null,
+                                     'tableName'      => null,
+                                     'inheritanceMap' => array(),
+                                     'enumMap'        => array(),
+                                     'type'           => null,
+                                     'charset'        => null,
+                                     'collation'      => null,
+                                     'indexes'        => array(),
+                                     'parents'        => array(),
+                                     'queryParts'     => array(),
+                                     'versioning'     => null,
+                                     'subclasses'     => array(),
+                                     );
+
+    protected $_ordering = null;
+
+    protected $_parser;
+
+    protected $_templates   = array();
+    protected $_invokedMethods = array();
+
+    public $listeners = array();
+
+    public function __construct($name, IPF_ORM_Connection $conn)
+    {
+        if (empty($name) || !class_exists($name))
+            throw new IPF_ORM_Exception("Couldn't find class " . $name);
+
+        $this->_conn = $conn;
+        $this->setParent($this->_conn);
+
+        $this->_options['name'] = $name;
+        $this->_parser = new IPF_ORM_Relation_Parser($this);
+
+        $this->initParents($name);
+
+        // create database table
+        $name::setTableDefinition($this);
+
+        if (!isset($this->_options['tableName'])) {
+            $this->setTableName(IPF_ORM_Inflector::tableize($class->getName()));
+        }
+
+        $this->initIdentifier();
+
+        $name::setUp($this);
+
+        $this->_repository = new IPF_ORM_Table_Repository($this);
+    }
+
+    private function initParents($name)
+    {
+        $names = array();
+
+        $class = $name;
+        do {
+            if ($class === 'IPF_ORM_Record')
+                break;
+
+            $name = $class;
+            $names[] = $name;
+        } while ($class = get_parent_class($class));
+
+        if ($class === false)
+            throw new IPF_ORM_Exception('Class "' . $name . '" must be a child class of IPF_ORM_Record');
+
+        // reverse names
+        $names = array_reverse($names);
+        // save parents
+        array_pop($names);
+        $this->_options['parents'] = $names;
+    }
+
+    public function initIdentifier()
+    {
+        switch (count($this->_identifier)) {
+            case 0:
+                $definition = array('type' => 'integer',
+                                    'length' => 20,
+                                    'autoincrement' => true,
+                                    'primary' => true);
+                $this->setColumn('id', $definition['type'], $definition['length'], $definition, true);
+                $this->_identifier = 'id';
+                $this->_identifierType = IPF_ORM::IDENTIFIER_AUTOINC;
+                break;
+            case 1:
+                foreach ($this->_identifier as $pk) {
+                    $e = $this->getDefinitionOf($pk);
+
+                    $found = false;
+
+                    foreach ($e as $option => $value) {
+                        if ($found) {
+                            break;
+                        }
+
+                        $e2 = explode(':', $option);
+
+                        switch (strtolower($e2[0])) {
+                            case 'autoincrement':
+                            case 'autoinc':
+                                if ($value !== false) {
+                                    $this->_identifierType = IPF_ORM::IDENTIFIER_AUTOINC;
+                                    $found = true;
+                                }
+                                break;
+                        }
+                    }
+                    if ( ! isset($this->_identifierType)) {
+                        $this->_identifierType = IPF_ORM::IDENTIFIER_NATURAL;
+                    }
+                }
+
+                $this->_identifier = $pk;
+
+                break;
+            default:
+                $this->_identifierType = IPF_ORM::IDENTIFIER_COMPOSITE;
+        }
+    }
+
+    public function isIdentifier($fieldName)
+    {
+        return ($fieldName === $this->getIdentifier() ||
+                in_array($fieldName, (array) $this->getIdentifier()));
+    }
+
+    public function isIdentifierAutoincrement()
+    {
+        return $this->getIdentifierType() === IPF_ORM::IDENTIFIER_AUTOINC;
+    }
+
+    public function isIdentifierComposite()
+    {
+        return $this->getIdentifierType() === IPF_ORM::IDENTIFIER_COMPOSITE;
+    }
+
+    public function getMethodOwner($method)
+    {
+        return (isset($this->_invokedMethods[$method])) ?
+                      $this->_invokedMethods[$method] : false;
+    }
+
+    public function setMethodOwner($method, $class)
+    {
+        $this->_invokedMethods[$method] = $class;
+    }
+
+    public function getTemplates()
+    {
+        return $this->_templates;
+    }
+
+    public function getRelationParser()
+    {
+        return $this->_parser;
+    }
+
+    public function __get($option)
+    {
+        if (isset($this->_options[$option])) {
+            return $this->_options[$option];
+        }
+        return null;
+    }
+
+    public function __isset($option)
+    {
+        return isset($this->_options[$option]);
+    }
+
+    public function getOptions()
+    {
+        return $this->_options;
+    }
+
+    public function setOptions($options)
+    {
+        foreach ($options as $key => $value) {
+            $this->setOption($key, $value);
+        }
+    }
+
+    public function addCheckConstraint($definition, $name)
+    {
+        if (is_string($name)) {
+            $this->_options['checks'][$name] = $definition;
+        } else {
+            $this->_options['checks'][] = $definition;
+        }
+
+        return $this;
+    }
+
+    public function addIndex($index, array $definition)
+    {
+        $this->_options['indexes'][$index] = $definition;
+    }
+
+    public function getIndex($index)
+    {
+        if (isset($this->_options['indexes'][$index])) {
+            return $this->_options['indexes'][$index];
+        }
+
+        return false;
+    }
+
+    public function bind($class, $alias, $type, array $options)
+    {
+        $this->_parser->bind($class, $alias, $type, $options);
+    }
+
+    public function ownsOne($class, $alias, $options=array())
+    {
+        $this->bind($class, $alias, IPF_ORM_Relation::ONE_COMPOSITE, $options);
+    }
+
+    public function ownsMany($class, $alias, $options=array())
+    {
+        $this->bind($class, $alias, IPF_ORM_Relation::MANY_COMPOSITE, $options);
+    }
+
+    public function hasOne($class, $alias, $options=array())
+    {
+        $this->bind($class, $alias, IPF_ORM_Relation::ONE_AGGREGATE, $options);
+    }
+
+    public function hasMany($class, $alias, $options=array())
+    {
+        $this->bind($class, $alias, IPF_ORM_Relation::MANY_AGGREGATE, $options);
+    }
+
+    public function hasRelation($alias)
+    {
+        return $this->_parser->hasRelation($alias);
+    }
+
+    public function getRelation($alias)
+    {
+        return $this->_parser->getRelation($alias);
+    }
+
+    public function getRelations()
+    {
+        return $this->_parser->getRelations();
+    }
+
+    public function createQuery($alias = '')
+    {
+        if ( ! empty($alias)) {
+            $alias = ' ' . trim($alias);
+        }
+        return IPF_ORM_Query::create($this->_conn)->from($this->getComponentName() . $alias);
+    }
+
+    public function getRepository()
+    {
+        return $this->_repository;
+    }
+
+    public function setOption($name, $value)
+    {
+        switch ($name) {
+            case 'name':
+            case 'tableName':
+                break;
+            case 'enumMap':
+            case 'inheritanceMap':
+            case 'index':
+                if (!is_array($value))
+                    throw new IPF_ORM_Exception($name . ' should be an array.');
+                break;
+        }
+        $this->_options[$name] = $value;
+    }
+
+    public function getOption($name, $default=null)
+    {
+        if (isset($this->_options[$name])) {
+            return $this->_options[$name];
+        }
+        return $default;
+    }
+
+    public function getColumnName($fieldName)
+    {
+        // FIX ME: This is being used in places where an array is passed, but it should not be an array
+        // For example in places where IPF_ORM should support composite foreign/primary keys
+        $fieldName = is_array($fieldName) ? $fieldName[0]:$fieldName;
+
+        if (isset($this->_columnNames[$fieldName])) {
+            return $this->_columnNames[$fieldName];
+        }
+
+        return strtolower($fieldName);
+    }
+
+    public function getColumnDefinition($columnName, $default=false)
+    {
+        if (isset($this->_columns[$columnName])) {
+            return $this->_columns[$columnName];
+        }
+        return false;
+    }
+
+    public function getFieldName($columnName)
+    {
+        if (isset($this->_fieldNames[$columnName])) {
+            return $this->_fieldNames[$columnName];
+        }
+        return $columnName;
+    }
+    public function setColumns(array $definitions)
+    {
+        foreach ($definitions as $name => $options) {
+            $this->setColumn($name, $options['type'], $options['length'], $options);
+        }
+    }
+
+    public function setColumn($name, $type, $length = null, $options = array(), $prepend = false)
+    {
+        if (is_string($options)) {
+            $options = explode('|', $options);
+        }
+
+        foreach ($options as $k => $option) {
+            if (is_numeric($k)) {
+                if (!empty($option)) {
+                    $options[$option] = true;
+                }
+                unset($options[$k]);
+            }
+        }
+
+        // extract column name & field name
+        if (stripos($name, ' as '))
+        {
+            if (strpos($name, ' as')) {
+                $parts = explode(' as ', $name);
+            } else {
+                $parts = explode(' AS ', $name);
+            }
+
+            if (count($parts) > 1) {
+                $fieldName = $parts[1];
+            } else {
+                $fieldName = $parts[0];
+            }
+
+            $name = strtolower($parts[0]);
+        } else {
+            $fieldName = $name;
+            $name = strtolower($name);
+        }
+
+        $name = trim($name);
+        $fieldName = trim($fieldName);
+
+        if ($prepend) {
+            $this->_columnNames = array_merge(array($fieldName => $name), $this->_columnNames);
+            $this->_fieldNames = array_merge(array($name => $fieldName), $this->_fieldNames);
+        } else {
+            $this->_columnNames[$fieldName] = $name;
+            $this->_fieldNames[$name] = $fieldName;
+        }
+
+        if ($length == null) {
+            switch ($type) {
+                case 'string':
+                case 'clob':
+                case 'float':
+                case 'double':
+                case 'integer':
+                case 'array':
+                case 'object':
+                case 'blob':
+                case 'gzip':
+                    // use php int max
+                    $length = 2147483647;
+                break;
+                case 'boolean':
+                    $length = 1;
+                case 'date':
+                    // YYYY-MM-DD ISO 8601
+                    $length = 10;
+                case 'time':
+                    // HH:NN:SS+00:00 ISO 8601
+                    $length = 14;
+                case 'timestamp':
+                    // YYYY-MM-DDTHH:MM:SS+00:00 ISO 8601
+                    $length = 25;
+                break;
+            }
+        }
+
+        $options['type'] = $type;
+        $options['length'] = $length;
+
+        if ($prepend) {
+            $this->_columns = array_merge(array($name => $options), $this->_columns);
+        } else {
+            $this->_columns[$name] = $options;
+        }
+
+        if (isset($options['primary']) && $options['primary']) {
+            if (isset($this->_identifier)) {
+                $this->_identifier = (array) $this->_identifier;
+            }
+            if ( ! in_array($fieldName, $this->_identifier)) {
+                $this->_identifier[] = $fieldName;
+            }
+        }
+        if (isset($options['default'])) {
+            $this->hasDefaultValues = true;
+        }
+    }
+
+    public function hasDefaultValues()
+    {
+        return $this->hasDefaultValues;
+    }
+
+    public function getDefaultValueOf($fieldName)
+    {
+        $columnName = $this->getColumnName($fieldName);
+        if ( ! isset($this->_columns[$columnName])) {
+            throw new IPF_ORM_Exception("Couldn't get default value. Column ".$columnName." doesn't exist.");
+        }
+        if (isset($this->_columns[$columnName]['default'])) {
+            return $this->_columns[$columnName]['default'];
+        } else {
+            return null;
+        }
+    }
+
+    public function getIdentifier()
+    {
+        return $this->_identifier;
+    }
+
+    public function getIdentifierType()
+    {
+        return $this->_identifierType;
+    }
+
+    public function hasColumn($columnName)
+    {
+        return isset($this->_columns[strtolower($columnName)]);
+    }
+
+    public function hasField($fieldName)
+    {
+        return isset($this->_columnNames[$fieldName]);
+    }
+
+    public function setSubClasses(array $map)
+    {
+        $class = $this->getComponentName();
+        if (isset($map[$class])) {
+            $this->setOption('inheritanceMap', $map[$class]);
+        } else {
+            $this->setOption('subclasses', array_keys($map));
+        }
+    }
+
+    public function setConnection(IPF_ORM_Connection $conn)
+    {
+        $this->_conn = $conn;
+
+        $this->setParent($this->_conn);
+
+        return $this;
+    }
+
+    public function getConnection()
+    {
+        return $this->_conn;
+    }
+
+    public function create(array $array = array())
+    {
+        $record = new $this->_options['name']($this, true);
+        $record->fromArray($array);
+
+        return $record;
+    }
+
+    public function find($id, $hydrationMode = null)
+    {
+        if (is_null($id)) {
+            return false;
+        }
+
+        $id = is_array($id) ? array_values($id) : array($id);
+        
+        $q = $this->createQuery();
+        $q->where(implode(' = ? AND ', (array) $this->getIdentifier()) . ' = ?', $id)
+                ->limit(1);
+        $res = $q->fetchOne(array(), $hydrationMode);
+        $q->free();
+        
+        return $res;
+    }
+
+    public function findAll($hydrationMode = null)
+    {
+        $q = $this->createQuery();
+        if ($this->_ordering) {
+            $q->orderBy(implode(', ', $this->_ordering));
+        } elseif ($this->hasTemplate('IPF_ORM_Template_Orderable')) {
+            $q->orderBy($this->getTemplate('IPF_ORM_Template_Orderable')->getColumnName());
+        }
+        return $q->execute(array(), $hydrationMode);
+    }
+
+    public function findBySql($dql, $params = array(), $hydrationMode = null)
+    {
+        return $this->createQuery()->where($dql)->execute($params, $hydrationMode);
+    }
+
+    public function findByDql($dql, $params = array(), $hydrationMode = null)
+    {
+        $parser = new IPF_ORM_Query($this->_conn);
+        $component = $this->getComponentName();
+        $query = 'FROM ' . $component . ' WHERE ' . $dql;
+
+        return $parser->query($query, $params, $hydrationMode);
+    }
+
+    public function findOneByDql($dql, $params = array(), $hydrationMode = null)
+    {
+       $results = $this->findByDql($dql, $params, $hydrationMode);
+        if (is_array($results) && isset($results[0])) {
+            return $results[0];
+        } else if ($results instanceof IPF_ORM_Collection && $results->count() > 0) {
+            return $results->getFirst();
+        } else {
+            return false;
+        }
+    }
+    
+    
+    public function execute($queryKey, $params = array(), $hydrationMode = IPF_ORM::HYDRATE_RECORD)
+    {
+        return IPF_ORM_Manager::getInstance()
+                            ->getQueryRegistry()
+                            ->get($queryKey, $this->getComponentName())
+                            ->execute($params, $hydrationMode);
+    }
+
+    public function executeOne($queryKey, $params = array(), $hydrationMode = IPF_ORM::HYDRATE_RECORD)
+    {
+        return IPF_ORM_Manager::getInstance()
+                            ->getQueryRegistry()
+                            ->get($queryKey, $this->getComponentName())
+                            ->fetchOne($params, $hydrationMode);
+    }
+
+    public function clear()
+    {
+        $this->_identityMap = array();
+    }
+
+    public function addRecord(IPF_ORM_Record $record)
+    {
+        $id = implode(' ', $record->identifier());
+
+        if (isset($this->_identityMap[$id])) {
+            return false;
+        }
+
+        $this->_identityMap[$id] = $record;
+
+        return true;
+    }
+
+    public function removeRecord(IPF_ORM_Record $record)
+    {
+        $id = implode(' ', $record->identifier());
+
+        if (isset($this->_identityMap[$id])) {
+            unset($this->_identityMap[$id]);
+            return true;
+        }
+
+        return false;
+    }
+
+    public function getRecord()
+    {
+        if ( ! empty($this->_data)) {
+            $identifierFieldNames = $this->getIdentifier();
+
+            if ( ! is_array($identifierFieldNames)) {
+                $identifierFieldNames = array($identifierFieldNames);
+            }
+
+            $found = false;
+            foreach ($identifierFieldNames as $fieldName) {
+                if ( ! isset($this->_data[$fieldName])) {
+                    // primary key column not found return new record
+                    $found = true;
+                    break;
+                }
+                $id[] = $this->_data[$fieldName];
+            }
+
+            if ($found) {
+                $recordName = $this->getComponentName();
+                $record = new $recordName($this, true);
+                $this->_data = array();
+                return $record;
+            }
+
+
+            $id = implode(' ', $id);
+
+            if (isset($this->_identityMap[$id])) {
+                $record = $this->_identityMap[$id];
+                $record->hydrate($this->_data);
+            } else {
+                $recordName = $this->getComponentName();
+                $record = new $recordName($this);
+                $this->_identityMap[$id] = $record;
+            }
+            $this->_data = array();
+        } else {
+            $recordName = $this->getComponentName();
+            $record = new $recordName($this, true);
+        }
+
+        return $record;
+    }
+
+    final public function applyInheritance($where)
+    {
+        if ( ! empty($this->_options['inheritanceMap'])) {
+            $a = array();
+            foreach ($this->_options['inheritanceMap'] as $field => $value) {
+                $a[] = $this->getColumnName($field) . ' = ?';
+            }
+            $i = implode(' AND ', $a);
+            $where .= ' AND ' . $i;
+        }
+        return $where;
+    }
+
+    public function count()
+    {
+        $a = $this->_conn->execute('SELECT COUNT(1) FROM ' . $this->_options['tableName'])->fetch(IPF_ORM::FETCH_NUM);
+        return current($a);
+    }
+
+    public function getQueryObject()
+    {
+        $graph = new IPF_ORM_Query($this->getConnection());
+        $graph->load($this->getComponentName());
+        return $graph;
+    }
+
+    public function getEnumValues($fieldName)
+    {
+        $columnName = $this->getColumnName($fieldName);
+        if (isset($this->_columns[$columnName]['values'])) {
+            return $this->_columns[$columnName]['values'];
+        } else {
+            return array();
+        }
+    }
+
+    public function enumValue($fieldName, $index)
+    {
+        if (IPF_ORM_Null::isNull($index)) {
+            return $index;
+        }
+
+        $columnName = $this->getColumnName($fieldName);
+        if ( ! $this->_conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)
+            && isset($this->_columns[$columnName]['values'][$index])
+        ) {
+            return $this->_columns[$columnName]['values'][$index];
+        }
+
+        return $index;
+    }
+
+    public function enumIndex($fieldName, $value)
+    {
+        $values = $this->getEnumValues($fieldName);
+
+        $index = array_search($value, $values);
+        if ($index === false || !$this->_conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) {
+            return $index;
+        }
+        return $value;
+    }
+
+    public function getColumnCount()
+    {
+        return count($this->_columns);
+    }
+
+    public function getColumns()
+    {
+        return $this->_columns;
+    }
+
+    public function removeColumn($fieldName)
+    {
+        if ($this->hasField($fieldName)) {
+            $columnName = $this->getColumnName($fieldName);
+            unset($this->_columnNames[$fieldName], $this->_fieldNames[$columnName], $this->_columns[$columnName]);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public function getColumnNames(array $fieldNames = null)
+    {
+        if ($fieldNames === null) {
+            return array_keys($this->_columns);
+        } else {
+           $columnNames = array();
+           foreach ($fieldNames as $fieldName) {
+               $columnNames[] = $this->getColumnName($fieldName);
+           }
+           return $columnNames;
+        }
+    }
+
+    public function getIdentifierColumnNames()
+    {
+        return $this->getColumnNames((array) $this->getIdentifier());
+    }
+
+    public function getFieldNames()
+    {
+        return array_values($this->_fieldNames);
+    }
+
+    public function getDefinitionOf($fieldName)
+    {
+        $columnName = $this->getColumnName($fieldName);
+        return $this->getColumnDefinition($columnName);
+    }
+
+    public function getTypeOf($fieldName)
+    {
+        return $this->getTypeOfColumn($this->getColumnName($fieldName));
+    }
+
+    public function getTypeOfColumn($columnName)
+    {
+        return isset($this->_columns[$columnName]) ? $this->_columns[$columnName]['type'] : false;
+    }
+
+    public function setData(array $data)
+    {
+        $this->_data = $data;
+    }
+
+    public function getData()
+    {
+        return $this->_data;
+    }
+
+    public function prepareValue($fieldName, $value, $typeHint = null)
+    {
+        if (IPF_ORM_Null::isNull($value)) {
+            return $value;
+        } else if ($value === null) {
+            return null;
+        } else {
+            $type = is_null($typeHint) ? $this->getTypeOf($fieldName) : $typeHint;
+
+            switch ($type) {
+                case 'integer':
+                case 'string';
+                    // don't do any casting here PHP INT_MAX is smaller than what the databases support
+                break;
+                case 'enum':
+                    return $this->enumValue($fieldName, $value);
+                break;
+                case 'boolean':
+                    return (boolean) $value;
+                break;
+                case 'array':
+                case 'object':
+                    if (is_string($value)) {
+                        $value = empty($value) ? null:unserialize($value);
+
+                        if ($value === false) {
+                            throw new IPF_ORM_Exception('Unserialization of ' . $fieldName . ' failed.');
+                        }
+                        return $value;
+                    }
+                break;
+                case 'gzip':
+                    $value = gzuncompress($value);
+
+                    if ($value === false) {
+                        throw new IPF_ORM_Exception('Uncompressing of ' . $fieldName . ' failed.');
+                    }
+                    return $value;
+                break;
+            }
+        }
+        return $value;
+    }
+
+    public function getComponentName()
+    {
+        return $this->_options['name'];
+    }
+
+    public function getTableName()
+    {
+        return $this->_options['tableName'];
+    }
+
+    public function setTableName($tableName)
+    {
+        $this->setOption('tableName', $tableName);
+    }
+
+    public function getTemplate($template)
+    {
+        if (!isset($this->_templates[$template]))
+            throw new IPF_ORM_Exception('Template ' . $template . ' not loaded');
+        return $this->_templates[$template];
+    }
+
+    public function hasTemplate($template)
+    {
+        return isset($this->_templates[$template]);
+    }
+
+    public function addTemplate($tpl, array $options=array())
+    {
+        if (!is_object($tpl)) {
+            $className = 'IPF_ORM_Template_' . $tpl;
+
+            if (class_exists($className, true)) {
+                $tpl = new $className($options);
+            } else if (class_exists($tpl, true)) {
+                $tpl = new $tpl($options);
+            } else {
+                throw new IPF_ORM_Record_Exception('Could not load behavior named: "' . $tpl . '"');
+            }
+        }
+
+        if (!($tpl instanceof IPF_ORM_Template)) {
+            throw new IPF_ORM_Record_Exception('Loaded behavior class is not an istance of IPF_ORM_Template.');
+        }
+
+        $className = get_class($tpl);
+        $this->_templates[$className] = $tpl;
+
+        $tpl->setTableDefinition($this);
+    }
+
+    public function setOrdering($ordering)
+    {
+        $this->_ordering = $ordering;
+    }
+
+    public function getOrdering()
+    {
+        return $this->_ordering;
+    }
+
+    public function bindQueryParts(array $queryParts)
+    {
+        $this->_options['queryParts'] = $queryParts;
+        return $this;
+    }
+
+    public function bindQueryPart($queryPart, $value)
+    {
+        $this->_options['queryParts'][$queryPart] = $value;
+        return $this;
+    }
+
+    public function getFieldValidators($fieldName)
+    {
+        $validators = array();
+        $columnName = $this->getColumnName($fieldName);
+        // this loop is a dirty workaround to get the validators filtered out of
+        // the options, since everything is squeezed together currently
+        
+        if (!isset($this->_columns[$columnName]))
+            return array();
+        
+        foreach ($this->_columns[$columnName] as $name => $args) {
+             if (empty($name)
+                    || $name == 'primary'
+                    || $name == 'protected'
+                    || $name == 'autoincrement'
+                    || $name == 'default'
+                    || $name == 'values'
+                    || $name == 'zerofill'
+                    || $name == 'owner'
+                    || $name == 'scale'
+                    || $name == 'type'
+                    || $name == 'length'
+                    || $name == 'fixed') {
+                continue;
+            }
+            if ($name == 'notnull' && isset($this->_columns[$columnName]['autoincrement'])) {
+                continue;
+            }
+            // skip it if it's explicitly set to FALSE (i.e. notnull => false)
+            if ($args === false) {
+                continue;
+            }
+            $validators[$name] = $args;
+        }
+
+        return $validators;
+    }
+
+    public function getBoundQueryPart($queryPart)
+    {
+        if (isset($this->_options['queryParts'][$queryPart]))
+            return $this->_options['queryParts'][$queryPart];
+        else
+            return null;
+    }
+
+    public function __toString()
+    {
+        return IPF_ORM_Utils::getTableAsString($this);
+    }
+
+    protected function findBy($fieldName, $value, $hydrationMode = null)
+    {
+        $q = $this->createQuery()->where($fieldName . ' = ?', array($value));
+        if ($this->_ordering)
+            $q->orderBy(implode(', ', $this->_ordering));
+        return $q->execute(array(), $hydrationMode);
+    }
+
+    protected function findOneBy($fieldName, $value, $hydrationMode = null)
+    {
+        $results = $this->createQuery()
+                        ->where($fieldName . ' = ?',array($value))
+                        ->limit(1)
+                        ->execute(array(), $hydrationMode);
+
+        if (is_array($results) && isset($results[0])) {
+            return $results[0];
+        } else if ($results instanceof IPF_ORM_Collection && $results->count() > 0) {
+            return $results->getFirst();
+        } else {
+            return false;
+        }
+    }
+
+    protected function _resolveFindByFieldName($name)
+    {
+        $fieldName = IPF_ORM_Inflector::tableize($name);
+        if ($this->hasColumn($name) || $this->hasField($name)) {
+            return $this->getFieldName($this->getColumnName($name));
+        } else if ($this->hasColumn($fieldName) || $this->hasField($fieldName)) {
+            return $this->getFieldName($this->getColumnName($fieldName));
+        } else {
+            return false;
+        }
+    }
+
+    public function __call($method, $arguments)
+    {
+        if (substr($method, 0, 6) == 'findBy') {
+            $by = substr($method, 6, strlen($method));
+            $method = 'findBy';
+        } else if (substr($method, 0, 9) == 'findOneBy') {
+            $by = substr($method, 9, strlen($method));
+            $method = 'findOneBy';
+        }
+
+        if (isset($by)) {
+            if ( ! isset($arguments[0])) {
+                throw new IPF_ORM_Exception('You must specify the value to findBy');
+            }
+
+            $fieldName = $this->_resolveFindByFieldName($by);
+            $hydrationMode = isset($arguments[1]) ? $arguments[1]:null;
+            if ($this->hasField($fieldName)) {
+                return $this->$method($fieldName, $arguments[0], $hydrationMode);
+            } else if ($this->hasRelation($by)) {
+                $relation = $this->getRelation($by);
+
+                if ($relation['type'] === IPF_ORM_Relation::MANY) {
+                    throw new IPF_ORM_Exception('Cannot findBy many relationship.');
+                }
+
+                return $this->$method($relation['local'], $arguments[0], $hydrationMode);
+            } else {
+                throw new IPF_ORM_Exception('Cannot find by: ' . $by . '. Invalid column or relationship alias.');
+            }
+        }
+
+        throw new IPF_ORM_Exception(sprintf('Unknown method %s::%s', get_class($this), $method));
+    }
+
+    public function notifyRecordListeners($method, $event)
+    {
+        foreach ($this->listeners as $listener)
+            if (is_callable(array($listener, $method)))
+                $listener->$method($event);
+    }
+}
+
diff --git a/src/orm/table/repository.php b/src/orm/table/repository.php
new file mode 100644 (file)
index 0000000..1fd1557
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+
+class IPF_ORM_Table_Repository implements Countable, IteratorAggregate
+{
+    private $table;
+    private $registry = array();
+
+    public function __construct(IPF_ORM_Table $table)
+    {
+        $this->table = $table;
+    }
+
+    public function getTable()
+    {
+        return $this->table;
+    }
+
+    public function add(IPF_ORM_Record $record)
+    {
+        $oid = $record->getOID();
+
+        if (isset($this->registry[$oid])) {
+            return false;
+        }
+        $this->registry[$oid] = $record;
+
+        return true;
+    }
+
+    public function get($oid)
+    {
+        if ( ! isset($this->registry[$oid])) {
+            throw new IPF_ORM_Exception("Unknown object identifier");
+        }
+        return $this->registry[$oid];
+    }
+
+    public function count()
+    {
+        return count($this->registry);
+    }
+
+    public function evict($oid)
+    {
+        if ( ! isset($this->registry[$oid])) {
+            return false;
+        }
+        unset($this->registry[$oid]);
+        return true;
+    }
+
+    public function evictAll()
+    {
+        $evicted = 0;
+        foreach ($this->registry as $oid=>$record) {
+            if ($this->evict($oid)) {
+                $evicted++;
+            }
+        }
+        return $evicted;
+    }
+
+    public function getIterator()
+    {
+        return new ArrayIterator($this->registry);
+    }
+
+    public function contains($oid)
+    {
+        return isset($this->registry[$oid]);
+    }
+
+    public function loadAll()
+    {
+        $this->table->findAll();
+    }
+}
\ No newline at end of file
diff --git a/src/orm/template.php b/src/orm/template.php
new file mode 100644 (file)
index 0000000..376a859
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+abstract class IPF_ORM_Template
+{
+    protected $_invoker;
+
+    public function setInvoker(IPF_ORM_Record $invoker)
+    {
+        $this->_invoker = $invoker;
+    }
+
+    public function getInvoker()
+    {
+        return $this->_invoker;
+    }
+
+    abstract public function setTableDefinition(IPF_ORM_Table $table);
+}
+
diff --git a/src/orm/template/listener/orderable.php b/src/orm/template/listener/orderable.php
new file mode 100644 (file)
index 0000000..157f492
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+
+class IPF_ORM_Template_Listener_Orderable
+{
+    private $columnName, $prepend;
+
+    public function __construct($columnName, $prepend)
+    {
+        $this->columnName = $columnName;
+        $this->prepend = $prepend;
+    }
+
+    public function preInsert(IPF_ORM_Event $event)
+    {
+        $this->setOrderValue($event->getInvoker());
+    }
+
+    public function preUpdate(IPF_ORM_Event $event)
+    {
+        $this->setOrderValue($event->getInvoker());
+    }
+
+    private function setOrderValue($obj)
+    {
+        $columnName = $this->columnName;
+        if ($obj->$columnName !== null)
+            return;
+
+        if ($this->prepend) {
+            $f = 'min';
+            $d = '-';
+        } else {
+            $f = 'max';
+            $d = '+';
+        }
+
+        $res = IPF_ORM_Query::create()
+            ->select('coalesce('.$f.'('.$this->columnName.') '.$d.' 1, 1) as x_ord')
+            ->from(get_class($obj))
+            ->execute();
+
+        $obj->$columnName = (int)$res[0]->x_ord;
+    }
+}
+
diff --git a/src/orm/template/listener/owned.php b/src/orm/template/listener/owned.php
new file mode 100644 (file)
index 0000000..7c99b69
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+class IPF_ORM_Template_Listener_Owned
+{
+    private $columnName;
+
+    public function __construct($columnName)
+    {
+        $this->columnName = $columnName;
+    }
+
+    public function preInsert(IPF_ORM_Event $event)
+    {
+        $this->setOwner($event->getInvoker());
+    }
+
+    public function preUpdate(IPF_ORM_Event $event)
+    {
+        $this->setOwner($event->getInvoker());
+    }
+
+    private function setOwner($obj)
+    {
+        $columnName = $this->columnName;
+        if ($obj->$columnName)
+            return;
+
+        $request = IPF_Project::getInstance()->request;
+        if ($request && !$request->user->isAnonymous()) {
+            $obj->$columnName = $request->user->id;
+        }
+    }
+}
+
diff --git a/src/orm/template/listener/sluggable.php b/src/orm/template/listener/sluggable.php
new file mode 100644 (file)
index 0000000..eaf0735
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+
+class IPF_ORM_Template_Listener_Sluggable
+{
+    protected $_options = array();
+
+    public function __construct(array $options)
+    {
+        $this->_options = $options;
+    }
+
+    public function preInsert(IPF_ORM_Event $event)
+    {
+        $name = $this->_options['name'];
+
+        $record = $event->getInvoker();
+
+        if ( ! $record->$name) {
+            $record->$name = $this->buildSlug($record);
+        }
+    }
+
+    public function preUpdate(IPF_ORM_Event $event)
+    {
+        if (false !== $this->_options['unique']) {
+            $name = $this->_options['name'];
+    
+            $record = $event->getInvoker();
+
+            if ( ! $record->$name ||
+            (false !== $this->_options['canUpdate'] &&
+            array_key_exists($name, $record->getModified()))) {
+                $record->$name = $this->buildSlug($record);
+            }
+        }
+    }
+
+    protected function buildSlug($record)
+    {
+        if (empty($this->_options['fields'])) {
+            if (method_exists($record, 'getUniqueSlug')) {
+                $value = $record->getUniqueSlug($record);
+            } else {
+                $value = (string) $record;
+            }
+        } else {
+            if ($this->_options['unique'] === true) {   
+                $value = $this->getUniqueSlug($record);
+            } else {  
+                $value = '';
+                foreach ($this->_options['fields'] as $field) {
+                    $value .= $record->$field . ' ';
+                } 
+            }
+        }
+
+        $value =  call_user_func_array($this->_options['builder'], array($value, $record));
+
+        return $value;
+    }
+
+    public function getUniqueSlug($record)
+    {
+        $name = $this->_options['name'];
+        $slugFromFields = '';
+        foreach ($this->_options['fields'] as $field) {
+            $slugFromFields .= $record->$field . ' ';
+        }
+
+        $proposal = $record->$name ? $record->$name : $slugFromFields;
+        $proposal =  call_user_func_array($this->_options['builder'], array($proposal, $record));
+        $slug = $proposal;
+
+        $whereString = 'r.' . $name . ' LIKE ?';
+        $whereParams = array($proposal.'%');
+        
+        if ($record->exists()) {
+            $identifier = $record->identifier();
+            $whereString .= ' AND r.' . implode(' != ? AND r.', $record->getTable()->getIdentifierColumnNames()) . ' != ?';
+            $whereParams = array_merge($whereParams, array_values($identifier));
+        }
+
+        foreach ($this->_options['uniqueBy'] as $uniqueBy) {
+            if (is_null($record->$uniqueBy)) {
+                $whereString .= ' AND r.'.$uniqueBy.' IS NULL';
+            } else {
+                $whereString .= ' AND r.'.$uniqueBy.' = ?';
+                $whereParams[] =  $record->$uniqueBy;
+            }
+        }
+
+        $query = IPF_ORM_Query::create()
+        ->select('r.'.$name)
+        ->from(get_class($record).' r')
+        ->where($whereString , $whereParams)
+        ->setHydrationMode(IPF_ORM::HYDRATE_ARRAY);
+
+        $similarSlugResult = $query->execute();
+
+        $similarSlugs = array();
+        foreach ($similarSlugResult as $key => $value) {
+            $similarSlugs[$key] = $value[$name];
+        }
+
+        $i = 1;
+        while (in_array($slug, $similarSlugs)) {
+            $slug = $proposal.'-'.$i;
+            $i++;
+        }
+
+        return  $slug;
+    }
+}
diff --git a/src/orm/template/listener/timestampable.php b/src/orm/template/listener/timestampable.php
new file mode 100644 (file)
index 0000000..67afd8c
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+class IPF_ORM_Template_Listener_Timestampable
+{
+    protected $_options = array();
+    public function __construct(array $options)
+    {
+        $this->_options = $options;
+    }
+
+    public function preInsert(IPF_ORM_Event $event)
+    {
+        if( ! $this->_options['created']['disabled']) {
+            $createdName = $this->_options['created']['name'];
+            $event->getInvoker()->$createdName = $this->getTimestamp('created');
+        }
+
+        if( ! $this->_options['updated']['disabled'] && $this->_options['updated']['onInsert']) {
+            $updatedName = $this->_options['updated']['name'];
+            $event->getInvoker()->$updatedName = $this->getTimestamp('updated');
+        }
+    }
+
+    public function preUpdate(IPF_ORM_Event $event)
+    {
+        if( ! $this->_options['updated']['disabled']) {
+            $updatedName = $this->_options['updated']['name'];
+            $event->getInvoker()->$updatedName = $this->getTimestamp('updated');
+        }
+    }
+
+    public function getTimestamp($type)
+    {
+        $options = $this->_options[$type];
+
+        if ($options['expression'] !== false && is_string($options['expression'])) {
+            return new IPF_ORM_Expression($options['expression']);
+        } else {
+            if ($options['type'] == 'date') {
+                return gmdate($options['format']);
+            } else if ($options['type'] == 'timestamp') {
+                return gmdate($options['format']);
+            } else {
+                return gmmktime();
+            }
+        }
+    }
+}
diff --git a/src/orm/template/orderable.php b/src/orm/template/orderable.php
new file mode 100644 (file)
index 0000000..4ed0a33
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+class IPF_ORM_Template_Orderable extends IPF_ORM_Template
+{
+    private $columnName = 'ord';
+    private $exclude = true;
+    private $prepend = false;
+
+    public function __construct(array $options=array())
+    {
+        if ($options) {
+            if (array_key_exists('name', $options))
+                $this->columnName = $options['name'];
+            if (array_key_exists('exclude', $options))
+                $this->exclude = $options['exclude'];
+            if (array_key_exists('prepend', $options))
+                $this->prepend = $options['prepend'];
+        }
+    }
+
+    public function getColumnName()
+    {
+        return $this->columnName;
+    }
+
+    public function setTableDefinition(IPF_ORM_Table $table)
+    {
+        $table->setColumn($this->columnName, 'integer', null, array('exclude' => $this->exclude));
+        $table->addIndex($table->getOption('tableName') . '_orderable_' . $this->columnName, array('fields' => array($this->columnName)));
+        $table->listeners['Orderable_'.$this->columnName] = new IPF_ORM_Template_Listener_Orderable($this->columnName, $this->prepend);
+    }
+}
+
diff --git a/src/orm/template/owned.php b/src/orm/template/owned.php
new file mode 100644 (file)
index 0000000..8134201
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+class IPF_ORM_Template_Owned extends IPF_ORM_Template
+{
+    private $name = 'owner';
+    private $columnName = 'owner_id';
+    private $exclude = true;
+    private $verbose = 'owner';
+
+    public function __construct(array $options=array())
+    {
+        if ($options) {
+            if (array_key_exists('column', $options))
+                $this->columnName = $options['column'];
+            if (array_key_exists('name', $options))
+                $this->name = $options['name'];
+            if (array_key_exists('exclude', $options))
+                $this->exclude = $options['exclude'];
+            if (array_key_exists('verbose', $options))
+                $this->verbose = $options['verbose'];
+        }
+    }
+
+    public function getColumnName()
+    {
+        return $this->columnName;
+    }
+
+    public function setTableDefinition(IPF_ORM_Table $table)
+    {
+        $table->setColumn($this->columnName, 'integer', null, array(
+            'exclude'   => $this->exclude,
+            'verbose'   => $this->verbose,
+        ));
+
+        $fks = $table->getOption('foreignKeys', array());
+        $fks[] = array(
+            'local'        => $this->columnName,
+            'foreign'      => 'id',
+            'foreignTable' => 'auth_users',
+            'onUpdate'     => null,
+            'onDelete'     => 'CASCADE',
+        );
+        $table->setOption('foreignKeys', $fks);
+
+        $table->listeners['Owned_'.$this->columnName] = new IPF_ORM_Template_Listener_Owned($this->columnName);
+    }
+}
+
diff --git a/src/orm/template/sluggable.php b/src/orm/template/sluggable.php
new file mode 100644 (file)
index 0000000..30d14ed
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+class IPF_ORM_Template_Sluggable extends IPF_ORM_Template
+{
+    protected $_options = array('name'          =>  'slug',
+                                'type'          =>  'string',
+                                'length'        =>  255,
+                                'unique'        =>  true,
+                                'options'       =>  array(),
+                                'fields'        =>  array(),
+                                'uniqueBy'      =>  array(),
+                                'uniqueIndex'   =>  true,
+                                'canUpdate'     =>  false,
+                                'builder'       =>  array('IPF_ORM_Inflector', 'urlize'),
+                                'indexName'     =>  'sluggable'
+    );
+
+    public function __construct(array $options = array())
+    {
+        $this->_options = IPF_ORM_Utils::arrayDeepMerge($this->_options, $options);
+    }
+
+    public function setTableDefinition(IPF_ORM_Table $table)
+    {
+        $table->setColumn($this->_options['name'], $this->_options['type'], $this->_options['length'], $this->_options['options']);
+
+        if ($this->_options['unique'] == true && $this->_options['uniqueIndex'] == true && !empty($this->_options['fields'])) {
+            $indexFields = array($this->_options['name']);
+            $indexFields = array_merge($indexFields, $this->_options['uniqueBy']);
+            $table->addIndex($this->_options['indexName'], array('fields' => $indexFields, 'type' => 'unique'));
+        }
+        $table->listeners['Sluggable_'.print_r($this->_options, true)] = new IPF_ORM_Template_Listener_Sluggable($this->_options);
+    }
+}
+
diff --git a/src/orm/template/timestampable.php b/src/orm/template/timestampable.php
new file mode 100644 (file)
index 0000000..117b025
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+
+class IPF_ORM_Template_Timestampable extends IPF_ORM_Template
+{
+    protected $_options = array(
+        'created' => array(
+            'name'        => 'created_at',
+            'type'        => 'timestamp',
+            'format'      => 'Y-m-d H:i:s',
+            'disabled'    => false,
+            'expression'  => false,
+            'options'     => array('exclude' => true),
+        ),
+        'updated' => array(
+            'name'        => 'updated_at',
+            'type'        => 'timestamp',
+            'format'      => 'Y-m-d H:i:s',
+            'disabled'    => false,
+            'expression'  => false,
+            'onInsert'    => true,
+            'exclude'     => true,
+            'options'     => array('exclude' => true),
+        ),
+    );
+
+    public function __construct(array $options = array())
+    {
+        $this->_options = IPF_ORM_Utils::arrayDeepMerge($this->_options, $options);
+    }
+
+    public function setTableDefinition(IPF_ORM_Table $table)
+    {
+        if (!$this->_options['created']['disabled']) {
+            $table->setColumn($this->_options['created']['name'], $this->_options['created']['type'], null, $this->_options['created']['options']);
+        }
+        if (!$this->_options['updated']['disabled']) {
+            $table->setColumn($this->_options['updated']['name'], $this->_options['updated']['type'], null, $this->_options['updated']['options']);
+        }
+        $table->listeners['Timestampable_'.print_r($this->_options, true)] = new IPF_ORM_Template_Listener_Timestampable($this->_options);
+    }
+}
+
diff --git a/src/orm/transaction.php b/src/orm/transaction.php
new file mode 100644 (file)
index 0000000..cc60631
--- /dev/null
@@ -0,0 +1,267 @@
+<?php
+
+class IPF_ORM_Transaction extends IPF_ORM_Connection_Module
+{
+    const STATE_SLEEP       = 0;
+    const STATE_ACTIVE      = 1;
+    const STATE_BUSY        = 2;
+
+    protected $_nestingLevel = 0;
+    protected $_internalNestingLevel = 0;
+    protected $invalid          = array();
+    protected $savePoints       = array();
+    protected $_collections     = array();
+
+    public function addCollection(IPF_ORM_Collection $coll)
+    {
+        $this->_collections[] = $coll;
+
+        return $this;
+    }
+
+    public function getState()
+    {
+        switch ($this->_nestingLevel) {
+            case 0:
+                return IPF_ORM_Transaction::STATE_SLEEP;
+                break;
+            case 1:
+                return IPF_ORM_Transaction::STATE_ACTIVE;
+                break;
+            default:
+                return IPF_ORM_Transaction::STATE_BUSY;
+        }
+    }
+
+    public function addInvalid(IPF_ORM_Record $record)
+    {
+        if (in_array($record, $this->invalid, true)) {
+            return false;
+        }
+        $this->invalid[] = $record;
+        return true;
+    }
+
+    public function getInvalid()
+    {
+        return $this->invalid;
+    }
+
+    public function getTransactionLevel()
+    {
+        return $this->_nestingLevel;
+    }
+    
+    public function getInternalTransactionLevel()
+    {
+        return $this->_internalNestingLevel;
+    }
+
+    public function beginTransaction($savepoint = null)
+    {
+        if ( ! is_null($savepoint)) {
+            $this->savePoints[] = $savepoint;
+
+            $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_CREATE);
+
+            $this->conn->notifyDBListeners('preSavepointCreate', $event);
+
+            if ( ! $event->skipOperation) {
+                $this->createSavePoint($savepoint);
+            }
+
+            $this->conn->notifyDBListeners('postSavepointCreate', $event);
+        } else {
+            if ($this->_nestingLevel == 0) {
+                $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_BEGIN);
+
+                $this->conn->notifyDBListeners('preTransactionBegin', $event);
+
+                if ( ! $event->skipOperation) {
+                    try {
+                        $this->_doBeginTransaction();
+                    } catch (Exception $e) {
+                        throw new IPF_ORM_Exception($e->getMessage());
+                    }
+                }
+                $this->conn->notifyDBListeners('postTransactionBegin', $event);
+            }
+        }
+
+        $level = ++$this->_nestingLevel;
+
+        return $level;
+    }
+
+    public function commit($savepoint = null)
+    {
+        if ($this->_nestingLevel == 0) {
+            throw new IPF_ORM_Exception("Commit failed. There is no active transaction.");
+        }
+
+        if ( ! is_null($savepoint)) {
+            $this->_nestingLevel -= $this->removeSavePoints($savepoint);
+
+            $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_COMMIT);
+
+            $this->conn->notifyDBListeners('preSavepointCommit', $event);
+
+            if ( ! $event->skipOperation) {
+                $this->releaseSavePoint($savepoint);
+            }
+
+            $this->conn->notifyDBListeners('postSavepointCommit', $event);
+        } else {
+                 
+            if ($this->_nestingLevel == 1 || $this->_internalNestingLevel == 1) {
+                if ( ! empty($this->invalid)) {
+                    if ($this->_internalNestingLevel == 1) {
+                        // transaction was started by IPF_ORM, so we are responsible
+                        // for a rollback
+                        $this->rollback();
+                        $tmp = $this->invalid;
+                        $this->invalid = array();
+                        throw new IPF_ORM_Exception_Validator($tmp);
+                    }
+                }
+                if ($this->_nestingLevel == 1) {
+                    // take snapshots of all collections used within this transaction
+                    foreach ($this->_collections as $coll) {
+                        $coll->takeSnapshot();
+                    }
+                    $this->_collections = array();
+
+                    $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_COMMIT);
+
+                    $this->conn->notifyDBListeners('preTransactionCommit', $event);
+                    if ( ! $event->skipOperation) {
+                        $this->_doCommit();
+                    }
+                    $this->conn->notifyDBListeners('postTransactionCommit', $event);
+                }
+            }
+            
+            if ($this->_nestingLevel > 0) {
+                $this->_nestingLevel--;
+            }            
+            if ($this->_internalNestingLevel > 0) {
+                $this->_internalNestingLevel--;
+            } 
+        }
+
+        return true;
+    }
+
+    public function rollback($savepoint = null)
+    {
+        if ($this->_nestingLevel == 0) {
+            throw new IPF_ORM_Exception("Rollback failed. There is no active transaction.");
+        }
+
+        if ($this->_internalNestingLevel > 1 || $this->_nestingLevel > 1) {
+            $this->_internalNestingLevel--;
+            $this->_nestingLevel--;
+            return false;
+        }
+
+        if ( ! is_null($savepoint)) {
+            $this->_nestingLevel -= $this->removeSavePoints($savepoint);
+
+            $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_ROLLBACK);
+
+            $this->conn->notifyDBListeners('preSavepointRollback', $event);
+            
+            if ( ! $event->skipOperation) {
+                $this->rollbackSavePoint($savepoint);
+            }
+
+            $this->conn->notifyDBListeners('postSavepointRollback', $event);
+        } else {
+            $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_ROLLBACK);
+    
+            $this->conn->notifyDBListeners('preTransactionRollback', $event);
+            
+            if ( ! $event->skipOperation) {
+                $this->_nestingLevel = 0;
+                $this->_internalNestingLevel = 0;
+                try {
+                    $this->_doRollback();
+                } catch (Exception $e) {
+                    throw new IPF_ORM_Exception($e->getMessage());
+                }
+            }
+
+            $this->conn->notifyDBListeners('postTransactionRollback', $event);
+        }
+
+        return true;
+    }
+
+    protected function createSavePoint($savepoint)
+    {
+        throw new IPF_ORM_Exception('Savepoints not supported by this driver.');
+    }
+
+    protected function releaseSavePoint($savepoint)
+    {
+        throw new IPF_ORM_Exception('Savepoints not supported by this driver.');
+    }
+
+    protected function rollbackSavePoint($savepoint)
+    {
+        throw new IPF_ORM_Exception('Savepoints not supported by this driver.');
+    }
+    
+    protected function _doRollback()
+    {
+        $this->conn->getDbh()->rollback();
+    }
+    
+    protected function _doCommit()
+    {
+        $this->conn->getDbh()->commit();
+    }
+    
+    protected function _doBeginTransaction()
+    {
+        $this->conn->getDbh()->beginTransaction();
+    }
+
+    private function removeSavePoints($savepoint)
+    {
+        $this->savePoints = array_values($this->savePoints);
+
+        $found = false;
+        $i = 0;
+
+        foreach ($this->savePoints as $key => $sp) {
+            if ( ! $found) {
+                if ($sp === $savepoint) {
+                    $found = true;
+                }
+            }
+            if ($found) {
+                $i++;
+                unset($this->savePoints[$key]);
+            }
+        }
+
+        return $i;
+    }
+
+    public function setIsolation($isolation)
+    {
+        throw new IPF_ORM_Exception('Transaction isolation levels not supported by this driver.');
+    }
+
+    public function getIsolation()
+    {
+        throw new IPF_ORM_Exception('Fetching transaction isolation level not supported by this driver.');
+    }
+    
+    public function beginInternalTransaction($savepoint = null)
+    {
+        $this->_internalNestingLevel++;
+        return $this->beginTransaction($savepoint);
+    }
+}
diff --git a/src/orm/transaction/mysql.php b/src/orm/transaction/mysql.php
new file mode 100644 (file)
index 0000000..20597ff
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+
+class IPF_ORM_Transaction_Mysql extends IPF_ORM_Transaction
+{
+    protected function createSavePoint($savepoint)
+    {
+        $query = 'SAVEPOINT ' . $savepoint;
+
+        return $this->conn->execute($query);
+    }
+
+    protected function releaseSavePoint($savepoint)
+    {
+        $query = 'RELEASE SAVEPOINT ' . $savepoint;
+
+        return $this->conn->execute($query);
+    }
+
+    protected function rollbackSavePoint($savepoint)
+    {
+        $query = 'ROLLBACK TO SAVEPOINT ' . $savepoint;
+
+        return $this->conn->execute($query);
+    }
+
+    public function setIsolation($isolation)
+    {
+        switch ($isolation) {
+            case 'READ UNCOMMITTED':
+            case 'READ COMMITTED':
+            case 'REPEATABLE READ':
+            case 'SERIALIZABLE':
+                break;
+            default:
+                throw new IPF_ORM_Exception('Isolation level ' . $isolation . ' is not supported.');
+        }
+
+        $query = 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $isolation;
+
+        return $this->conn->execute($query);
+    }
+
+    public function getIsolation()
+    {
+        return $this->conn->fetchOne('SELECT @@tx_isolation');
+    }
+}
\ No newline at end of file
diff --git a/src/orm/utils.php b/src/orm/utils.php
new file mode 100644 (file)
index 0000000..e6b55f2
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+
+class IPF_ORM_Utils {
+
+    public static function getConnectionStateAsString($state)
+    {
+        switch ($state) {
+            case IPF_ORM_Transaction::STATE_SLEEP:
+                return "open";
+                break;
+            case IPF_ORM_Transaction::STATE_BUSY:
+                return "busy";
+                break;
+            case IPF_ORM_Transaction::STATE_ACTIVE:
+                return "active";
+                break;
+        }
+    }
+
+    public static function getConnectionAsString(IPF_ORM_Connection $connection)
+    {
+        $r[] = '<pre>';
+        $r[] = 'IPF_ORM_Connection object';
+        $r[] = 'State               : ' . IPF_ORM_Utils::getConnectionStateAsString($connection->transaction->getState());
+        $r[] = 'Open Transactions   : ' . $connection->transaction->getTransactionLevel();
+        $r[] = 'Table in memory     : ' . $connection->count();
+        $r[] = 'Driver name         : ' . $connection->getDriverName();
+        $r[] = "</pre>";
+        return implode("\n",$r)."<br>";
+    }
+    
+    public static function getValidators()
+    {
+        return array(
+            'country',
+            'creditcard',
+            'date',
+            'driver',
+            'email',
+            'exception',
+            'future',
+            'htmlcolor',
+            'ip',
+            'minlength',
+            'nospace',
+            'notblank',
+            'notnull',
+            'past',
+            'range',
+            'readonly',
+            'regexp',
+            'time',
+            'timestamp',
+            'unique',
+            'unsigned',
+            'usstate',
+            'file',
+            'image',
+            'html',
+            'uploadTo',
+            'verbose',
+        );
+    }
+
+    
+    public static function arrayDeepMerge()
+    {
+         switch (func_num_args()) {
+             case 0:
+                return false;
+             case 1:
+                return func_get_arg(0);
+             case 2:
+                $args = func_get_args();
+                $args[2] = array();
+                
+                if (is_array($args[0]) && is_array($args[1]))
+                {
+                    foreach (array_unique(array_merge(array_keys($args[0]),array_keys($args[1]))) as $key)
+                    {
+                        $isKey0 = array_key_exists($key, $args[0]);
+                        $isKey1 = array_key_exists($key, $args[1]);
+
+                        if ($isKey0 && $isKey1 && is_array($args[0][$key]) && is_array($args[1][$key]))
+                        {
+                            $args[2][$key] = self::arrayDeepMerge($args[0][$key], $args[1][$key]);
+                        } else if ($isKey0 && $isKey1) {
+                            $args[2][$key] = $args[1][$key];
+                        } else if ( ! $isKey1) {
+                            $args[2][$key] = $args[0][$key];
+                        } else if ( ! $isKey0) {
+                            $args[2][$key] = $args[1][$key];
+                        }
+                    }
+
+                    return $args[2];
+                } else {
+                    return $args[1];
+                }
+            default:
+                $args = func_get_args();
+                $args[1] = self::arrayDeepMerge($args[0], $args[1]);
+                array_shift($args);
+                return call_user_func_array(array('IPF', 'arrayDeepMerge'), $args);
+            break;
+        }
+    }        
+
+    public static function getTableAsString(IPF_ORM_Table $table)
+    {
+        $r[] = "<pre>";
+        $r[] = "Component   : ".$table->getComponentName();
+        $r[] = "Table       : ".$table->getTableName();
+        $r[] = "</pre>";
+        
+        return implode("\n",$r)."<br>";
+    }
+
+    public static function getCollectionAsString(IPF_ORM_Collection $collection)
+    {
+        $r[] = "<pre>";
+        $r[] = get_class($collection);
+        $r[] = 'data : ' . IPF_ORM::dump($collection->getData(), false);
+        //$r[] = 'snapshot : ' . IPF_ORM::dump($collection->getSnapshot());
+        $r[] = "</pre>";
+        return implode("\n",$r);
+    }
+}
+
diff --git a/src/orm/validator.php b/src/orm/validator.php
new file mode 100644 (file)
index 0000000..104d089
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+
+class IPF_ORM_Validator
+{
+    private static $validators = array();
+    public static function getValidator($name)
+    {
+        if ( ! isset(self::$validators[$name])) {
+            $class = 'IPF_ORM_Validator_' . ucfirst($name);
+            if (class_exists($class)) {
+                self::$validators[$name] = new $class;
+            } else if (class_exists($name)) {
+                self::$validators[$name] = new $name;
+            } else {
+                throw new IPF_ORM_Exception("Validator named '$name' not available.");
+            }
+
+        }
+        return self::$validators[$name];
+    }
+
+    public function validateRecord(IPF_ORM_Record $record)
+    {
+        $table = $record->getTable();
+
+        // if record is transient all fields will be validated
+        // if record is persistent only the modified fields will be validated
+        $fields = $record->exists() ? $record->getModified():$record->getData();
+        foreach ($fields as $fieldName => $value) {
+            $this->validateField($table, $fieldName, $value, $record);
+        }
+    }
+
+    private function validateField(IPF_ORM_Table $table, $fieldName, $value, IPF_ORM_Record $record)
+    {
+        if (IPF_ORM_Null::isNull($value)) {
+            $value = null;
+        } else if ($value instanceof IPF_ORM_Record) {
+            $value = $value->getIncremented();
+        }
+
+        $dataType = $table->getTypeOf($fieldName);
+
+        // Validate field type
+        if (!IPF_ORM_Validator::isValidType($value, $dataType)) {
+            $record->addError($fieldName, 'type');
+        }
+
+        if ($dataType == 'enum') {
+            $enumIndex = $table->enumIndex($fieldName, $value);
+            if ($enumIndex === false) {
+                $record->addError($fieldName, 'enum');
+            }
+        }
+
+        // Validate field length
+        $definition = $table->getDefinitionOf($fieldName);
+        if (!$this->validateLength($value, $dataType, $definition['length'])) {
+            $record->addError($fieldName, 'length');
+        }
+
+        // Run all custom validators
+        foreach ($table->getFieldValidators($fieldName) as $validatorName => $args) {
+            if (!is_string($validatorName)) {
+                $validatorName = $args;
+                $args = array();
+            }
+
+            $validator = IPF_ORM_Validator::getValidator($validatorName);
+            $validator->invoker = $record;
+            $validator->field = $fieldName;
+            $validator->args = $args;
+            if (!$validator->validate($value)) {
+                $record->addError($fieldName, $validatorName);
+            }
+        }
+    }
+
+    public function validateLength($value, $type, $maximumLength)
+    {
+        if ($type == 'timestamp' || $type == 'integer' || $type == 'enum') {
+            return true;
+        } else if ($type == 'array' || $type == 'object') {
+            $length = strlen(serialize($value));
+        } else if ($type == 'decimal') {
+            if (!$maximumLength)
+                $maximumLength = 18;
+            $length = strlen($value);
+        } else {
+            $length = strlen($value);
+        }
+
+        if ($length > $maximumLength) {
+            return false;
+        }
+        return true;
+    }
+
+    public function hasErrors()
+    {
+        return (count($this->stack) > 0);
+    }
+
+     public static function isValidType($var, $type)
+     {
+         if ($var instanceof IPF_ORM_Expression) {
+             return true;
+         } else if ($var === null) {
+             return true;
+         } else if (is_object($var)) {
+             return $type == 'object';
+         }
+
+         switch ($type) {
+             case 'float':
+             case 'double':
+             case 'decimal':
+                 return (string)$var == strval(floatval($var));
+             case 'integer':
+                 return (string)$var == strval(intval($var));
+             case 'string':
+                 return is_string($var) || is_numeric($var);
+             case 'blob':
+             case 'clob':
+             case 'gzip':
+                 return is_string($var);
+             case 'array':
+                 return is_array($var);
+             case 'object':
+                 return is_object($var);
+             case 'boolean':
+                 return is_bool($var) || (is_numeric($var) && ($var == 0 || $var == 1));
+             case 'timestamp':
+                 $validator = self::getValidator('timestamp');
+                 return $validator->validate($var);
+             case 'time':
+                 $validator = self::getValidator('time');
+                 return $validator->validate($var);
+             case 'date':
+                 $validator = self::getValidator('date');
+                 return $validator->validate($var);
+             case 'enum':
+                 return is_string($var) || is_int($var);
+             default:
+                 return false;
+         }
+     }
+}
+
diff --git a/src/orm/validator/country.php b/src/orm/validator/country.php
new file mode 100644 (file)
index 0000000..966ede8
--- /dev/null
@@ -0,0 +1,261 @@
+<?php
+
+class IPF_ORM_Validator_Country
+{
+    private static $countries = array(
+        'ad' =>    'Andorra',
+        'ae' =>    'United Arab Emirates',
+        'af' =>    'Afghanistan',
+        'ag' =>    'Antigua and Barbuda',
+        'ai' =>    'Anguilla',
+        'al' =>    'Albania',
+        'am' =>    'Armenia',
+        'an' =>    'Netherlands Antilles',
+        'ao' =>    'Angola',
+        'aq' =>    'Antarctica',
+        'ar' =>    'Argentina',
+        'as' =>    'American Samoa',
+        'at' =>    'Austria',
+        'au' =>    'Australia',
+        'aw' =>    'Aruba',
+        'az' =>    'Azerbaijan',
+        'ba' =>    'Bosnia Hercegovina',
+        'bb' =>    'Barbados',
+        'bd' =>    'Bangladesh',
+        'be' =>    'Belgium',
+        'bf' =>    'Burkina Faso',
+        'bg' =>    'Bulgaria',
+        'bh' =>    'Bahrain',
+        'bi' =>    'Burundi',
+        'bj' =>    'Benin',
+        'bm' =>    'Bermuda',
+        'bn' =>    'Brunei Darussalam',
+        'bo' =>    'Bolivia',
+        'br' =>    'Brazil',
+        'bs' =>    'Bahamas',
+        'bt' =>    'Bhutan',
+        'bv' =>    'Bouvet Island',
+        'bw' =>    'Botswana',
+        'by' =>    'Belarus (Byelorussia)',
+        'bz' =>    'Belize',
+        'ca' =>    'Canada',
+        'cc' =>    'Cocos Islands',
+        'cd' =>    'Congo, The Democratic Republic of the',
+        'cf' =>    'Central African Republic',
+        'cg' =>    'Congo',
+        'ch' =>    'Switzerland',
+        'ci' =>    'Ivory Coast',
+        'ck' =>    'Cook Islands',
+        'cl' =>    'Chile',
+        'cm' =>    'Cameroon',
+        'cn' =>    'China',
+        'co' =>    'Colombia',
+        'cr' =>    'Costa Rica',
+        'cs' =>    'Czechoslovakia',
+        'cu' =>    'Cuba',
+        'cv' =>    'Cape Verde',
+        'cx' =>    'Christmas Island',
+        'cy' =>    'Cyprus',
+        'cz' =>    'Czech Republic',
+        'de' =>    'Germany',
+        'dj' =>    'Djibouti',
+        'dk' =>    'Denmark',
+        'dm' =>    'Dominica',
+        'do' =>    'Dominican Republic',
+        'dz' =>    'Algeria',
+        'ec' =>    'Ecuador',
+        'ee' =>    'Estonia',
+        'eg' =>    'Egypt',
+        'eh' =>    'Western Sahara',
+        'er' =>    'Eritrea',
+        'es' =>    'Spain',
+        'et' =>    'Ethiopia',
+        'fi' =>    'Finland',
+        'fj' =>    'Fiji',
+        'fk' =>    'Falkland Islands',
+        'fm' =>    'Micronesia',
+        'fo' =>    'Faroe Islands',
+        'fr' =>    'France',
+        'fx' =>    'France, Metropolitan FX',
+        'ga' =>    'Gabon',
+        'gb' =>    'United Kingdom (Great Britain)',
+        'gd' =>    'Grenada',
+        'ge' =>    'Georgia',
+        'gf' =>    'French Guiana',
+        'gh' =>    'Ghana',
+        'gi' =>    'Gibraltar',
+        'gl' =>    'Greenland',
+        'gm' =>    'Gambia',
+        'gn' =>    'Guinea',
+        'gp' =>    'Guadeloupe',
+        'gq' =>    'Equatorial Guinea',
+        'gr' =>    'Greece',
+        'gs' =>    'South Georgia and the South Sandwich Islands',
+        'gt' =>    'Guatemala',
+        'gu' =>    'Guam',
+        'gw' =>    'Guinea-bissau',
+        'gy' =>    'Guyana',
+        'hk' =>    'Hong Kong',
+        'hm' =>    'Heard and McDonald Islands',
+        'hn' =>    'Honduras',
+        'hr' =>    'Croatia',
+        'ht' =>    'Haiti',
+        'hu' =>    'Hungary',
+        'id' =>    'Indonesia',
+        'ie' =>    'Ireland',
+        'il' =>    'Israel',
+        'in' =>    'India',
+        'io' =>    'British Indian Ocean Territory',
+        'iq' =>    'Iraq',
+        'ir' =>    'Iran',
+        'is' =>    'Iceland',
+        'it' =>    'Italy',
+        'jm' =>    'Jamaica',
+        'jo' =>    'Jordan',
+        'jp' =>    'Japan',
+        'ke' =>    'Kenya',
+        'kg' =>    'Kyrgyzstan',
+        'kh' =>    'Cambodia',
+        'ki' =>    'Kiribati',
+        'km' =>    'Comoros',
+        'kn' =>    'Saint Kitts and Nevis',
+        'kp' =>    'North Korea',
+        'kr' =>    'South Korea',
+        'kw' =>    'Kuwait',
+        'ky' =>    'Cayman Islands',
+        'kz' =>    'Kazakhstan',
+        'la' =>    'Laos',
+        'lb' =>    'Lebanon',
+        'lc' =>    'Saint Lucia',
+        'li' =>    'Lichtenstein',
+        'lk' =>    'Sri Lanka',
+        'lr' =>    'Liberia',
+        'ls' =>    'Lesotho',
+        'lt' =>    'Lithuania',
+        'lu' =>    'Luxembourg',
+        'lv' =>    'Latvia',
+        'ly' =>    'Libya',
+        'ma' =>    'Morocco',
+        'mc' =>    'Monaco',
+        'md' =>    'Moldova Republic',
+        'mg' =>    'Madagascar',
+        'mh' =>    'Marshall Islands',
+        'mk' =>    'Macedonia, The Former Yugoslav Republic of',
+        'ml' =>    'Mali',
+        'mm' =>    'Myanmar',
+        'mn' =>    'Mongolia',
+        'mo' =>    'Macau',
+        'mp' =>    'Northern Mariana Islands',
+        'mq' =>    'Martinique',
+        'mr' =>    'Mauritania',
+        'ms' =>    'Montserrat',
+        'mt' =>    'Malta',
+        'mu' =>    'Mauritius',
+        'mv' =>    'Maldives',
+        'mw' =>    'Malawi',
+        'mx' =>    'Mexico',
+        'my' =>    'Malaysia',
+        'mz' =>    'Mozambique',
+        'na' =>    'Namibia',
+        'nc' =>    'New Caledonia',
+        'ne' =>    'Niger',
+        'nf' =>    'Norfolk Island',
+        'ng' =>    'Nigeria',
+        'ni' =>    'Nicaragua',
+        'nl' =>    'Netherlands',
+        'no' =>    'Norway',
+        'np' =>    'Nepal',
+        'nr' =>    'Nauru',
+        'nt' =>    'Neutral Zone',
+        'nu' =>    'Niue',
+        'nz' =>    'New Zealand',
+        'om' =>    'Oman',
+        'pa' =>    'Panama',
+        'pe' =>    'Peru',
+        'pf' =>    'French Polynesia',
+        'pg' =>    'Papua New Guinea',
+        'ph' =>    'Philippines',
+        'pk' =>    'Pakistan',
+        'pl' =>    'Poland',
+        'pm' =>    'St. Pierre and Miquelon',
+        'pn' =>    'Pitcairn',
+        'pr' =>    'Puerto Rico',
+        'pt' =>    'Portugal',
+        'pw' =>    'Palau',
+        'py' =>    'Paraguay',
+        'qa' =>    'Qatar',
+        're' =>    'Reunion',
+        'ro' =>    'Romania',
+        'ru' =>    'Russia',
+        'rw' =>    'Rwanda',
+        'sa' =>    'Saudi Arabia',
+        'sb' =>    'Solomon Islands',
+        'sc' =>    'Seychelles',
+        'sd' =>    'Sudan',
+        'se' =>    'Sweden',
+        'sg' =>    'Singapore',
+        'sh' =>    'St. Helena',
+        'si' =>    'Slovenia',
+        'sj' =>    'Svalbard and Jan Mayen Islands',
+        'sk' =>    'Slovakia (Slovak Republic)',
+        'sl' =>    'Sierra Leone',
+        'sm' =>    'San Marino',
+        'sn' =>    'Senegal',
+        'so' =>    'Somalia',
+        'sr' =>    'Suriname',
+        'st' =>    'Sao Tome and Principe',
+        'sv' =>    'El Salvador',
+        'sy' =>    'Syria',
+        'sz' =>    'Swaziland',
+        'tc' =>    'Turks and Caicos Islands',
+        'td' =>    'Chad',
+        'tf' =>    'French Southern Territories',
+        'tg' =>    'Togo',
+        'th' =>    'Thailand',
+        'tj' =>    'Tajikistan',
+        'tk' =>    'Tokelau',
+        'tm' =>    'Turkmenistan',
+        'tn' =>    'Tunisia',
+        'to' =>    'Tonga',
+        'tp' =>    'East Timor',
+        'tr' =>    'Turkey',
+        'tt' =>    'Trinidad, Tobago',
+        'tv' =>    'Tuvalu',
+        'tw' =>    'Taiwan',
+        'tz' =>    'Tanzania',
+        'ua' =>    'Ukraine',
+        'ug' =>    'Uganda',
+        'uk' =>    'United Kingdom',
+        'um' =>    'United States Minor Islands',
+        'us' =>    'United States of America',
+        'uy' =>    'Uruguay',
+        'uz' =>    'Uzbekistan',
+        'va' =>    'Vatican City',
+        'vc' =>    'Saint Vincent, Grenadines',
+        've' =>    'Venezuela',
+        'vg' =>    'Virgin Islands (British)',
+        'vi' =>    'Virgin Islands (USA)',
+        'vn' =>    'Viet Nam',
+        'vu' =>    'Vanuatu',
+        'wf' =>    'Wallis and Futuna Islands',
+        'ws' =>    'Samoa',
+        'ye' =>    'Yemen',
+        'yt' =>    'Mayotte',
+        'yu' =>    'Yugoslavia',
+        'za' =>    'South Africa',
+        'zm' =>    'Zambia',
+        'zr' =>    'Zaire',
+        'zw' =>    'Zimbabwe');
+
+    public static function getCountries()
+    {
+        return self::$countries;
+    }
+
+    public function validate($value)
+    {
+        $value = strtolower($value);
+        return isset(self::$countries[$value]);
+    }
+}
+
diff --git a/src/orm/validator/creditcard.php b/src/orm/validator/creditcard.php
new file mode 100644 (file)
index 0000000..62609ac
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+class IPF_ORM_Validator_Creditcard
+{                                                         
+    public function validate($value)
+    {
+        $card_regexes = array(
+            "/^4\d{12}(\d\d\d){0,1}$/"      => 'visa',
+            "/^5[12345]\d{14}$/"            => 'mastercard',
+            "/^3[47]\d{13}$/"               => 'amex',
+            "/^6011\d{12}$/"                => 'discover',
+            "/^30[012345]\d{11}$/"          => 'diners',
+            "/^3[68]\d{12}$/"               => 'diners',
+        );
+
+        $cardType = '';
+        foreach ($card_regexes as $regex => $type) {
+            if (preg_match($regex, $value)) {
+                 $cardType = $type;
+                 break;
+            }
+        }
+        if (!$cardType)
+            return false;
+
+        /* mod 10 checksum algorithm */
+        $revcode = strrev($value);
+        $checksum = 0;
+        for ($i = 0; $i < strlen($revcode); $i++) {
+            $currentNum = intval($revcode[$i]);
+            if ($i & 1) {               /* Odd position */
+                 $currentNum *= 2;
+            }
+            /* Split digits and add. */
+            $checksum += $currentNum % 10;
+            if ($currentNum > 9) {
+                 $checksum += 1;
+            }
+        }
+        return $checksum % 10 == 0;
+    }
+}
+
diff --git a/src/orm/validator/date.php b/src/orm/validator/date.php
new file mode 100644 (file)
index 0000000..b0916a9
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+class IPF_ORM_Validator_Date
+{
+    public function validate($value)
+    {
+        if ($value === null) {
+            return true;
+        }
+        $e = explode('-', $value);
+
+        if (count($e) !== 3) {
+            return false;
+        }
+        return checkdate($e[1], $e[2], $e[0]);
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/driver.php b/src/orm/validator/driver.php
new file mode 100644 (file)
index 0000000..324d9da
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+class IPF_ORM_Validator_Driver
+{
+    protected $_args = array();
+
+    public function __get($arg)
+    {
+        if (isset($this->_args[$arg])) {
+            return $this->_args[$arg];
+        }
+        return null;
+    }
+
+    public function __isset($arg)
+    {
+        return isset($this->_args[$arg]);
+    }
+
+    public function __set($arg, $value)
+    {
+        $this->_args[$arg] = $value;
+        
+        return $this;
+    }
+
+    public function getArg($arg)
+    {
+        if ( ! isset($this->_args[$arg])) {
+            throw new IPF_ORM_Exception_Validator('Unknown option ' . $arg);
+        }
+        return $this->_args[$arg];
+    }
+
+    public function setArg($arg, $value)
+    {
+        $this->_args[$arg] = $value;
+        
+        return $this;
+    }
+
+    public function getArgs()
+    {
+        return $this->_args;
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/email.php b/src/orm/validator/email.php
new file mode 100644 (file)
index 0000000..6e07505
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+
+class IPF_ORM_Validator_Email
+{
+    public function validate($value)
+    {
+        if ($value === null)
+            return true;
+        return IPF_Utils::isEmail($value);
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/exclude.php b/src/orm/validator/exclude.php
new file mode 100644 (file)
index 0000000..c97ea11
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+class IPF_ORM_Validator_Exclude
+{
+    public function validate($value)
+    {
+        return true;
+    }
+}
+
diff --git a/src/orm/validator/file.php b/src/orm/validator/file.php
new file mode 100644 (file)
index 0000000..b235643
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+
+class IPF_ORM_Validator_File
+{
+    public function validate($value){
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/future.php b/src/orm/validator/future.php
new file mode 100644 (file)
index 0000000..b075f38
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+class IPF_ORM_Validator_Future
+{
+    public function validate($value)
+    {
+        if ($value === null) {
+            return true;
+        }
+        $e = explode('-', $value);
+
+        if (count($e) !== 3) {
+            return false;
+        }
+        
+        if (is_array($this->args) && isset($this->args['timezone'])) {
+            switch (strtolower($this->args['timezone'])) {
+                case 'gmt':
+                    $now = gmdate("U") - date("Z");
+                    break;
+                default:
+                    $now = getdate();
+                    break;
+            }
+        } else {
+            $now = getdate();
+        }
+        
+        if ($now['year'] > $e[0]) {
+            return false;
+        } else if ($now['year'] == $e[0]) {
+            if ($now['mon'] > $e[1]) {
+                return false;
+            } else if ($now['mon'] == $e[1]) {
+                return $now['mday'] < $e[2];
+            } else {
+                return true;
+            }
+        } else {
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/html.php b/src/orm/validator/html.php
new file mode 100644 (file)
index 0000000..bd663d0
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+
+class IPF_ORM_Validator_Html
+{
+    public function validate($value){
+        return true;
+    }
+}
diff --git a/src/orm/validator/htmlcolor.php b/src/orm/validator/htmlcolor.php
new file mode 100644 (file)
index 0000000..59cf78d
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+class IPF_ORM_Validator_Htmlcolor
+{
+    public function validate($value)
+    {
+        if ( ! preg_match("/^#{0,1}[0-9a-fA-F]{6}$/", $value)) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/image.php b/src/orm/validator/image.php
new file mode 100644 (file)
index 0000000..fb2e1e8
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+
+class IPF_ORM_Validator_Image
+{
+    public function validate($value){
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/ip.php b/src/orm/validator/ip.php
new file mode 100644 (file)
index 0000000..8642f32
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+class IPF_ORM_Validator_Ip
+{
+    public function validate($value)
+    {
+        return (bool) ip2long(str_replace("\0", '', $value));
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/minlength.php b/src/orm/validator/minlength.php
new file mode 100644 (file)
index 0000000..35583bd
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+class IPF_ORM_Validator_Minlength 
+{
+    public function validate($value)
+    {
+        if (isset($this->args) && strlen($value) < $this->args) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/nospace.php b/src/orm/validator/nospace.php
new file mode 100644 (file)
index 0000000..b9f326c
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+class IPF_ORM_Validator_Nospace extends IPF_ORM_Validator_Driver
+{
+    public function validate($value)
+    {
+        return ($value === null || ! preg_match('/\s/', $value));
+    }
+}
diff --git a/src/orm/validator/notblank.php b/src/orm/validator/notblank.php
new file mode 100644 (file)
index 0000000..c3fcc81
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+class IPF_ORM_Validator_Notblank extends IPF_ORM_Validator_Driver
+{
+    public function validate($value)
+    {
+        return (trim($value) !== '' && $value !== null);
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/notnull.php b/src/orm/validator/notnull.php
new file mode 100644 (file)
index 0000000..a7ca31e
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+class IPF_ORM_Validator_Notnull extends IPF_ORM_Validator_Driver
+{
+    public function validate($value)
+    {
+        return ($value !== null);
+    }
+}
diff --git a/src/orm/validator/past.php b/src/orm/validator/past.php
new file mode 100644 (file)
index 0000000..b620013
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+class IPF_ORM_Validator_Past
+{
+    public function validate($value)
+    {
+        if ($value === null) {
+            return true;
+        }
+        $e = explode('-', $value);
+
+        if (count($e) !== 3) {
+            return false;
+        }
+        
+        if (is_array($this->args) && isset($this->args['timezone'])) {
+            switch (strtolower($this->args['timezone'])) {
+                case 'gmt':
+                    $now = gmdate("U") - date("Z");
+                    break;
+                default:
+                    $now = getdate();
+                    break;
+            }
+        } else {
+            $now = getdate();
+        }
+        
+        if ($now['year'] < $e[0]) {
+            return false;
+        } else if ($now['year'] == $e[0]) {
+            if ($now['mon'] < $e[1]) {
+                return false;
+            } else if ($now['mon'] == $e[1]) {
+                return $now['mday'] > $e[2];
+            } else {
+                return true;
+            }
+        } else {
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/range.php b/src/orm/validator/range.php
new file mode 100644 (file)
index 0000000..2a895d3
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+
+class IPF_ORM_Validator_Range
+{
+    public function validate($value)
+    {
+        if (isset($this->args[0]) && $value < $this->args[0]) {
+            return false;
+        }
+        if (isset($this->args[1]) && $value > $this->args[1]) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/readonly.php b/src/orm/validator/readonly.php
new file mode 100644 (file)
index 0000000..dac58f8
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+class IPF_ORM_Validator_Readonly
+{
+    public function validate($value)
+    {
+        $modified = $this->invoker->getModified();
+        return array_key_exists($this->field, $modified) ? false : true;
+    }
+}
diff --git a/src/orm/validator/regexp.php b/src/orm/validator/regexp.php
new file mode 100644 (file)
index 0000000..3981f13
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+class IPF_ORM_Validator_Regexp
+{
+    public function validate($value)
+    {
+        if ( ! isset($this->args)) {
+           return true;
+        }
+        if (is_array($this->args)) {
+            foreach ($this->args as $regexp) {
+                if ( ! preg_match($regexp, $value)) {
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            if (preg_match($this->args, $value)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/time.php b/src/orm/validator/time.php
new file mode 100644 (file)
index 0000000..7c90fd4
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+class IPF_ORM_Validator_Time
+{
+    public function validate($value)
+    {
+        if ($value === null) {
+            return true;
+        }
+
+        $e = explode(':', $value);
+
+        if (count($e) !== 3) {
+            return false;
+        }
+
+        if ( ! preg_match('/^ *[0-9]{2}:[0-9]{2}:[0-9]{2} *$/', $value)) {
+            return false;
+        }
+
+        $hr = intval($e[0], 10);
+        $min = intval($e[1], 10);
+        $sec = intval($e[2], 10);
+
+        return $hr >= 0 && $hr <= 23 && $min >= 0 && $min <= 59 && $sec >= 0 && $sec <= 59;      
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/timestamp.php b/src/orm/validator/timestamp.php
new file mode 100644 (file)
index 0000000..ab95161
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+class IPF_ORM_Validator_Timestamp
+{
+    public function validate($value)
+    {
+        if ($value === null) {
+            return true;
+        }
+
+        if ( ! preg_match('/^ *[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} *$/', $value)) {
+            return false;
+        }
+
+        list($date, $time) = explode(' ', trim($value));
+
+        $dateValidator = IPF_ORM_Validator::getValidator('date');
+        $timeValidator = IPF_ORM_Validator::getValidator('time');
+
+        if ( ! $dateValidator->validate($date)) {
+            return false;
+        }
+
+        if ( ! $timeValidator->validate($time)) {
+            return false;
+        }
+
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/unique.php b/src/orm/validator/unique.php
new file mode 100644 (file)
index 0000000..8f03404
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+class IPF_ORM_Validator_Unique
+{
+    public function validate($value)
+    {
+        $table = $this->invoker->getTable();
+        $pks = $table->getIdentifier();
+
+        if ( is_array($pks) ) {
+            $pks = join(',', $pks);
+        }
+
+        $sql   = 'SELECT ' . $pks . ' FROM ' . $table->getTableName() . ' WHERE ' . $this->field . ' = ?';
+        
+        $values = array();
+        $values[] = $value;
+        
+        // If the record is not new we need to add primary key checks because its ok if the 
+        // unique value already exists in the database IF the record in the database is the same
+        // as the one that is validated here.
+        $state = $this->invoker->state();
+        if ( ! ($state == IPF_ORM_Record::STATE_TDIRTY || $state == IPF_ORM_Record::STATE_TCLEAN)) {
+            foreach ((array) $table->getIdentifier() as $pk) {
+                $sql .= " AND {$pk} != ?";
+                $values[] = $this->invoker->$pk;
+            }
+        }
+        
+        $stmt  = $table->getConnection()->getDbh()->prepare($sql);
+        $stmt->execute($values);
+
+        return ( ! is_array($stmt->fetch()));
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/unsigned.php b/src/orm/validator/unsigned.php
new file mode 100644 (file)
index 0000000..7f6cf1e
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+class IPF_ORM_Validator_Unsigned
+{
+    public function validate($value)
+    {
+        $int = (int) $value;
+
+        if ($int != $value || $int < 0) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/uploadto.php b/src/orm/validator/uploadto.php
new file mode 100644 (file)
index 0000000..2a99a68
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+class IPF_ORM_Validator_UploadTo
+{
+    public function validate($value)
+    {
+        return true;
+    }
+}
+
diff --git a/src/orm/validator/usstate.php b/src/orm/validator/usstate.php
new file mode 100644 (file)
index 0000000..b76a1b1
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+class IPF_ORM_Validator_Usstate
+{
+    private static $states = array(
+                'AK' => true,
+                'AL' => true,
+                'AR' => true,
+                'AZ' => true,
+                'CA' => true,
+                'CO' => true,
+                'CT' => true,
+                'DC' => true,
+                'DE' => true,
+                'FL' => true,
+                'GA' => true,
+                'HI' => true,
+                'IA' => true,
+                'ID' => true,
+                'IL' => true,
+                'IN' => true,
+                'KS' => true,
+                'KY' => true,
+                'LA' => true,
+                'MA' => true,
+                'MD' => true,
+                'ME' => true,
+                'MI' => true,
+                'MN' => true,
+                'MO' => true,
+                'MS' => true,
+                'MT' => true,
+                'NC' => true,
+                'ND' => true,
+                'NE' => true,
+                'NH' => true,
+                'NJ' => true,
+                'NM' => true,
+                'NV' => true,
+                'NY' => true,
+                'OH' => true,
+                'OK' => true,
+                'OR' => true,
+                'PA' => true,
+                'PR' => true,
+                'RI' => true,
+                'SC' => true,
+                'SD' => true,
+                'TN' => true,
+                'TX' => true,
+                'UT' => true,
+                'VA' => true,
+                'VI' => true,
+                'VT' => true,
+                'WA' => true,
+                'WI' => true,
+                'WV' => true,
+                'WY' => true
+            );
+    public function getStates()
+    {
+        return self::$states;
+    }
+
+    public function validate($value)
+    {
+        return isset(self::$states[$value]);
+    }
+}
\ No newline at end of file
diff --git a/src/orm/validator/verbose.php b/src/orm/validator/verbose.php
new file mode 100644 (file)
index 0000000..0a6478e
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+class IPF_ORM_Validator_Verbose
+{
+    public function validate($value)
+    {
+        return true;
+    }
+}
+