<?php
require_once LIBRARY_PATH . '/Zend/Db/Table/Abstract.php';
/**
 * Goweb_Db_Model_Row
 */
Goweb::loadClass('Goweb_Db_Model_Row');

/**
 * Goweb_Db_Model_Rowset
 */
Goweb::loadClass('Goweb_Db_Model_Rowset');

/**
 * Goweb Sitemanager 5 Framework
 *
 * @version    5.1.4
 * @category   Goweb
 * @package    Goweb
 * @author     I&D <dev@goweb.pt>, Pedro Pinto <ppinto@goweb.pt>
 * @copyright  2010 (c) Goweb (http://www.goweb.pt)
 */

/**
 *
 * @version    5.1.4
 * @category   Goweb
 * @package    Goweb
 * @author     I&D <dev@goweb.pt>, Pedro Pinto <ppinto@goweb.pt>
 * @copyright  2010 (c) Goweb (http://www.goweb.pt)
 */
class Goweb_Db_Model extends Zend_Db_Table_Abstract{

    /**
     * Name of the row count column
     *
     * @var string
     */
    const ROW_COUNT_COLUMN = 'zend_paginator_row_count';

    /**
     * Guarda os models necess&aacute;rios das chaves externas na forma nome => nome do model
     *
     * @var  array
     */
    protected $_models = array();

    /**
     * Define as colunas para cada tab
     *
     * @var  array
     */
    protected $_tabs = array();

    /**
     * Flag para defenir um model com estrutura em àrvore (Tree)
     *
     * @var  boolean
     */
    protected $_isTree = false;

    /**
     * Guarda as relacoes com outros models na forma nome => nome do model
     *
     * @var  array
     */
    protected $_relations = array();

    /**
     * Flag para carregar as chaves estrangeiras
     *
     * @var  bool
     */
    protected $_loadForeigns = null;

    /**
     * Objecto Zend_Db_Adapter_Abstract.
     *
     * @var  Zend_Db_Adapter_Abstract
     */
    protected $_db;

    /**
     * Guarda os dados da linha da tabela num objecto Goweb_Db_Model_Row
     *
     * @var  Goweb_Db_Model_Row
     */
    protected $_row = null;

    /**
     * O objecto Zend_Db_Select
     *
     * @var  Zend_Db_Select
     */
    protected $_select = null;

    /**
     * Nome da tabela
     *
     * @var  string
     */
    protected $_table;

    /**
     * Nome do prefixo da tabela
     *
     * @var  string
     */
    protected $_tablePrefix;

    /**
     * Linguas suportadas
     *
     * @var  array
     */
    protected $_langs = null;

    /**
     * Lingua activa
     *
     * @var  string
     */
    protected $_lang;

    /**
     * Colunas a pesquisar
     *
     * @var  array
     */
    protected $_searchCols = array();

    /**
     * Criar indices de Pesquisa
     *
     * @var bool
     */
    protected $_useSearchIndex = false;

    /**
     * Link
     *
     * @var bool
     */
    protected $_link = null;

    /**
     * Colunas a sincronizar no caso de haver multilingue
     *
     * @var  array
     */
    protected $_syncCols = array();

    /**
     * As colunas da tabela na forma: coluna => Goweb_Db_Model_Col
     *
     * @var  array
     */
    protected $_colsModel = array();

    /**
     * Chave Primaria
     *
     * @var string
     */
    protected $_primaria = 'id';

    /**
     * Usar Cache
     *
     * @var bool
     */
    protected $_useCache = true;

    /**
     * Guarda os dados os nomes das chaves estrangeiras na forma: nome do model => model(Goweb_Db_Model)
     *
     * @var  array
     */
    protected $_foreigns = array();

    /**
     * Guarda os dados os nomes das chaves estrangeiras na forma: nome do model => model(Goweb_Db_Model)
     *
     * @var  array
     */
    protected $_modelJoins = array();

    /**
     * Retorna o row do model.
     *
     * @return  Goweb_Db_Model_Row  O row do model
     */
    public function getRow() {
        return clone($this->_row);
    }

    /**
     * Constructor.
     * @param  string|Goweb_Db_Adapter  $db  No do adapter no Goweb::Registry() ou um objecto do tipo Goweb_Db_Adapter
     */
    public function __construct($db = null, $loadForeigns = true) {
        $this->setRowClass('Goweb_Db_Model_Row');
        $this->setRowsetClass('Goweb_Db_Model_Rowset');
        $this->_loadForeigns = $loadForeigns;

        // verifica se um objecto Goweb_Db_Adapter foi passado
        if ($db == null) {
            // vai buscar o adapter Goweb_Db_Adapter nativo
            if (!$this->_db) {
                $this->_db = Goweb::registry('db');
            }
        } else {
            // se foi passado uma string, entco verifica se este no registry
            if (is_string($db)) {
                $this->_db = Goweb::registry($db);
            } elseif ($db instanceof Goweb_Db_Adapter || $db instanceof Zend_Db_Adapter_Abstract) { // foi passado o Goweb_Db_Adapter
                $this->_db = $db;
            }
        }

        // certifica que e um Goweb_Db_Adapter
        if (!$this->_db instanceof Goweb_Db_Adapter && !$this->_db instanceof Zend_Db_Adapter_Abstract) {
            throw new Goweb_Exception('O objecto contido em "Goweb_Db_Model::_db" n&atilde;o e do tipo "Goweb_Db_Adapter".');
        }

        // cria o objecto do tipo Goweb_Db_Model_Row
        $this->_row = new Goweb_Db_Model_Row();

        // atribui o nome do model
        $this->_row->setModelName(get_class($this));

        // cria o objecto do tipo Zend_Db_Select
        $this->_select = $this->_db->select();

        // define o prefixo da tabela, que podere ser util para as línguas
        $this->_tablePrefix = $this->_table;

        // vai buscar as configuracoes efectuadas pelo utilizador

        parent::__construct($this->_db);
        /**
         * Compatibilidade Zend->Goweb
         */
        $this->_primaria = $this->_primary;


        // inicializa os valores a null
        foreach ($this->_colsModel as $key => $col) {
            $this->_row->{$key} = null;
        }
        $this->_checkForeigns();

        // vai buscar o nome da tabela
        if (!$this->_table) {
            $this->_table = str_replace('_Model_', '', self::underscore(get_class($this)));
        }
        // Compatibilidade entre Zend e Goweb
        if (!$this->_name) $this->_name = $this->_table;

        //$this->_setupMetadata();

        // testa se foi definida alguma chave prim&aacute;ria
        if (!array_key_exists($this->_primaria, $this->_colsModel)) {
            throw new Goweb_Exception('O model "' . get_class($this) . '" n&atilde;o possui chave prim&aacute;ria.');
        }
    }

    /**
     * Verifica as Foreign Keys
     *
     * @return bool
     */
    protected function _checkForeigns(){
        if (!$this->_loadForeigns) return true;
        foreach ($this->_colsModel as $key => $col) {
            if (isset($this->_foreigns[$key])) continue;
            // se houver relacoes no campo entco cria um objecto Goweb_Db_Model_Row para cada uma delas
            if ($this->_loadForeigns && ($col->getType() == 'ForeignKey' || $col->getType() ==  'SelfKey')) {
                if ($col->getType() ==  'SelfKey') {
                    $foreignModel = get_class($this);
                    if (!$this->getIsTree()){
                        $this->setIsTree();
                    }
                } else {
                    $foreignModel = $col->getParam('model');
                }

                if (!array_key_exists($foreignModel, $this->_models)) {
                    $this->_models[$foreignModel] = Goweb::getModelInstance($foreignModel, $this->_db, false);
                }

                $foreignCols = $this->_models[$foreignModel]->getColsNames();
                $foreignTable = $this->_models[$foreignModel]->getTableName();
                if (!isset($this->_foreigns[$key])){
                    $this->_foreigns[$key] = $foreignModel;

                    if ($col->getParam('alias')) {
                        if (array_key_exists($col->getParam('alias'), $this->_colsModel)) {
                            throw new Goweb_Exception('Já existe uma coluna definida com o mesmo nome do "alias" definido no Model "'.$foreignModel.'".');
                        }
                        $foreignTable = $col->getParam('alias');
                    }
                    if (!isset($this->_row->{$foreignTable})){
                        $this->_row->{$foreignTable} = new Goweb_Db_Model_Row($this->_models[$foreignModel]->getToString());
                        $this->_row->{$foreignTable}->setFromArray(array_flip($foreignCols));
                        $this->_row->{$foreignTable}->setModelName($foreignModel);
                    }
                }
            }
        }

    }

    /**
     * Retorna TRUE se descobri uma relacao na ervore.
     *
     * @param   array  $relations  Array do tipo id => pai_id
     * @param   int    $child      Id do filho
     * @param   int    $parent     Id do pai
     * @param   int    $total      Total de relacoes
     * @return  bool   Se encontrou uma relacao
     */
    protected function _findRelation($relations, $parent, $child, $total) {
        if (array_key_exists($child, $relations) &&
        array_key_exists($relations[$child], $relations) &&
        $parent != $child && $total > 0) {
            $total--;
            return $this->_findRelation($relations, $parent, $relations[$child], $total);
        } elseif ($parent == $child) {
            return true;
        }
        return false;
    }

    /**
     * Remove linha da tabela em o model este a representar
     *
     * @return  int  N&uacute;mero de linhas removidas
     */
    public function delete($value = null) {
        $tag = array($this->_table);
        $rows = 0;

        // o model este carregado
        if ($this->isLoaded()) {
            $primary = $this->_db->quoteIdentifier($this->_primaria);
            if ($this->_langs) {
                foreach ($this->_langs as $lang) {
                    $lang = preg_replace('/([^a-z0-9_])/i', '_', $lang);
                    $tag[] = $this->_tablePrefix . '_' . $lang;
                    $rows = $this->_db->delete($this->_tablePrefix . '_' . $lang,  $this->_db->quoteInto("$primary = ?", $this->_row->{$this->_primaria}));
                    $this->_deleteSearch($lang, $this->_row->{$this->_primaria});
                }
            } else {
                $rows = $this->_db->delete($this->_table,  $this->_db->quoteInto("$primary = ?", $this->_row->{$this->_primaria}));
                foreach (goweb::registry('session')->langs as $lang){
                    $this->_deleteSearch($lang, $this->_row->{$this->_primaria});
                }
            }
            $this->_clearCache();
            return $rows;
        }

        // foi submetido um valor(s) para a chave prim&aacute;ria
        if ($value) {
            $value = (array) $value;
            $primary = $this->_db->quoteIdentifier($this->_primaria);
            if (count($value) > 1) {
                if ($this->_langs) {
                    foreach ($this->_langs as $lang) {
                        $lang = preg_replace('/([^a-z0-9_])/i', '_', $lang);
                        $tag[] = $this->_tablePrefix . '_' . $lang;
                        $rows = $this->_db->delete($this->_tablePrefix . '_' . $lang, "$primary IN(" . $this->_db->quote($value) . ")");
                        $this->_deleteSearch($lang, $value);
                    }
                } else {
                    $rows = $this->_db->delete($this->_table, "$primary IN(" . $this->_db->quote($value) . ")");
                    foreach (goweb::registry('session')->langs as $lang){
                        $this->_deleteSearch($lang, $value);
                    }
                }
            } elseif (isset($value[0])) {
                if ($this->_langs) {
                    foreach ($this->_langs as $lang) {
                        $lang = preg_replace('/([^a-z0-9_])/i', '_', $lang);
                        $tag[] = $this->_tablePrefix . '_' . $lang;
                        $rows = $this->_db->delete($this->_tablePrefix . '_' . $lang, $this->_db->quoteInto("$primary = ?", $value[0]));
                        foreach (goweb::registry('session')->langs as $lang){
                            $this->_deleteSearch($lang, $value);
                        }
                    }
                } else {
                    $rows = $this->_db->delete($this->_table, $this->_db->quoteInto("$primary = ?", $value[0]));
                    foreach (goweb::registry('session')->langs as $lang){
                        $this->_deleteSearch($lang, $value);
                    }
                }
            }
        }
        $this->_clearCache();
        // retorna 0 se o model n&atilde;o este carregado e se n&atilde;o foi passado nenhum valor(s)
        return $rows;
    }

    /**
     * Remove linha(s) de uma tabela atraves de uma clausula WHERE
     *
     * @param   string  $where  A clausula WHERE
     *
     * @return  int  N&uacute;mero de linhas removidas
     */
    public function deleteWhere($where) {
        $tag = array($this->_table);
        if ($this->_langs) {
            foreach ($this->_langs as $lang) {
                $lang = preg_replace('/([^a-z0-9_])/i', '_', $lang);
                $tag[] = $this->_tablePrefix . '_' . $lang;
                $rows = $this->_db->delete($this->_tablePrefix . '_' . $lang, $where);
            }
        } else {
            $rows = $this->_db->delete($this->_table, $where);
        }
        $this->_clearCache();
        return $rows;
    }

    /**
     * Carrega o model atraves da chave prim&aacute;ria
     *
     * @param   string  $value  Valor da chave prim&aacute;ria a procurar
     * @return  bool                  TRUE se encontrou alguma linha com o(s) valor(es) submetidos
     */
    public function find($value) {
        $this->_select = $this->_db->select();
        $key = $this->_table . '.' . $this->_db->quoteIdentifier($this->_primaria);
        $where = $this->_db->quoteInto("$key = ?", $value);

        $this->_select->where($where);
        $this->_select->from($this->_table, $this->_getColsWithPrefix($this->_table, array_keys($this->_colsModel)));

        if ($this->_loadForeigns) {
            // adiciona as campos das chaves estrangeiras
            foreach ($this->_foreigns as $col => $model) {
                $alias = $this->_colsModel[$col]->getParam('alias');
                $table = $this->_models[$model]->getTableName();
                /*
                 $tableAlias = ($this->_colsModelModel[$col]->getParam('type') == 'SelfKey') ? $alias : null;
                 $tablePrefix = ($this->_colsModelModel[$col]->getParam('type') == 'SelfKey') ? $alias : $table;
                 */
                $tableAlias = $tablePrefix = ($alias ? $alias : $table);

                $where = $this->_table . '.' . $col . '=' . $tablePrefix . '.' . $this->_models[$model]->getPrimary();
                $foreignCols = $this->_getColsWithPrefix($tablePrefix, $this->_models[$model]->getColsNames());
                $this->_select->joinLeft(array($tableAlias => $table), $where, $foreignCols);
            }
        }

        if ($this->_useCache){
            $cache = Zend_Registry::get('cache');
            if(($result = $cache->load($this->_table . '_' . md5($this->_select->__toString()))) === false) {
                $result = $this->_db->fetchRow($this->_select);
                $cache->save($result, $this->_table . '_' . md5($this->_select->__toString()), array($this->_table));
            }
        } else {
            $result = $this->_db->fetchRow($this->_select);
        }

        // limpa as configuracoes do $this->select, criando uma nova instancia
        $this->_select = $this->_db->select();

        // se sucesso, popula o Goweb_Db_Model::_rows com os valores
        if ($result) {
            foreach (array_keys($this->_colsModel) as $col) {
                /**
                 * fix para sqlserver, que limita a 32 caracteres o nome da coluna!
                 */
                if (!array_key_exists($this->_table . '_' . $col, $result)){
                    $one = substr($this->_table . '_' . $col,0,31);
                    if (array_key_exists($one, $result))
                        $this->_row->{$col} = $result[$one];
                    else
                        $this->_row->{$col} = $result[substr($this->_table . '_' . $col,0,30)];
                } else {
                    $this->_row->{$col} = $result[$this->_table . '_' . $col];
                }
            }

            if ($this->_loadForeigns) {
                foreach ($this->_foreigns as $col => $model) {
                    $alias = $table = $this->_models[$model]->getTableName();
                    if ($this->_colsModel[$col]->getParam('alias')) {
                        $alias = $this->_colsModel[$col]->getParam('alias');
                    }

                    $row = array();
                    foreach (array_keys($this->_row->{$alias}->toArray()) as $foreignCol) {
                        /**
                         * fix para sqlserver, que limita a 32 caracteres o nome da coluna!
                         */
                        if (!array_key_exists($alias . '_' . $foreignCol, $result)){
                            $one = substr($alias . '_' . $foreignCol,0,31);
                            if (array_key_exists($one, $result)){
                                $row[$foreignCol] = $result[$one];
                            } else {
                                $row[$foreignCol] = $result[substr($alias . '_' . $foreignCol,0,30)];
                            }
                        } else {
                            $row[$foreignCol] = $result[$alias . '_' . $foreignCol];
                        }
                    }
                    $this->_row->{$alias}->setFromArray($row);
                }
            }
        }

        if ($result) {
            return true;
        }
        return false;
    }

    /**
     * Carrega o model atraves de um valor de uma coluna
     *
     * @param   string  $col    Nome da coluna
     * @param   string  $value  Valor a procurar
     * @return  bool            TRUE se encontrou alguma linha com o(s) valor(es) submetidos
     */
    public function findBy($col, $value) {
        if (!array_key_exists($col, $this->_colsModel)) {
            throw new Goweb_Exception("A coluna '$col' n&atilde;o existe no model '" . get_class($this) ."'.");
        }
        $this->_select = $this->_db->select();
        $key = $this->_table . '.' . $this->_db->quoteIdentifier($col);
        $where = $this->_db->quoteInto("$key = ?", $value);
        $this->_select->where($where);
        $this->_select->from($this->_table, $this->_getColsWithPrefix($this->_table, array_keys($this->_colsModel)));

        if ($this->_loadForeigns) {
            // adiciona as campos das chaves estrangeiras
            foreach ($this->_foreigns as $col => $model) {
                $alias = $this->_colsModel[$col]->getParam('alias');
                $table = $this->_models[$model]->getTableName();
                $tableAlias = ($this->_colsModel[$col]->getParam('type') == 'SelfKey') ? $alias : null;
                $tablePrefix = (($this->_colsModel[$col]->getParam('type') == 'SelfKey') ? $alias : $table);

                $where = $this->_table . '.' . $col . '=' . $tablePrefix . '.' . $this->_models[$model]->getPrimary();
                $foreignCols = $this->_getColsWithPrefix($tablePrefix, $this->_models[$model]->getColsNames());
                $this->_select->joinLeft($table, $where, $foreignCols);
            }
        }

        if ($this->_useCache){
            $cache = Zend_Registry::get('cache');
            if(($result = $cache->load($this->_table . '_' . md5($this->_select->__toString()))) === false) {
                $result = $this->_db->fetchRow($this->_select);
                $cache->save($result, $this->_table . '_' . md5($this->_select->__toString()), array($this->_table));
            }
        } else {
            $result = $this->_db->fetchRow($this->_select);
        }
        
        // limpa as configuracoes do $this->select, criando uma nova instancia
        $this->_select = $this->_db->select();

        // se sucesso, popula o Goweb_Db_Model::_rows com os valores
        if ($result) {
            foreach (array_keys($this->_colsModel) as $col) {
                /**
                 * fix para sqlserver, que limita a 32 caracteres o nome da coluna!
                 */
                if (!array_key_exists($this->_table . '_' . $col, $result)){
                    $one = substr($this->_table . '_' . $col,0,31);
                    if (array_key_exists($one, $result))
                        $this->_row->{$col} = $result[$one];
                    else
                        $this->_row->{$col} = $result[substr($this->_table . '_' . $col,0,30)];
                } else {
                    $this->_row->{$col} = $result[$this->_table . '_' . $col];
                }
            }

            if ($this->_loadForeigns) {
                foreach ($this->_foreigns as $col => $model) {
                    $alias = $table = $this->_models[$model]->getTableName();
                    if ($this->_colsModel[$col]->getParam('alias')) {
                        $alias = $this->_colsModel[$col]->getParam('alias');
                    }

                    $row = array();
                    foreach (array_keys($this->_row->{$alias}->toArray()) as $foreignCol) {
                        /**
                         * fix para sqlserver, que limita a 32 caracteres o nome da coluna!
                         */
                        if (!array_key_exists($table . '_' . $foreignCol, $result)){
                            $one = substr($table . '_' . $foreignCol,0,31);
                            if (array_key_exists($one, $result)){
                                $row[$foreignCol] = $result[$one];
                            } else {
                                $row[$foreignCol] = $result[substr($table . '_' . $foreignCol,0,30)];
                            }
                        } else {
                            $row[$foreignCol] = $result[$table . '_' . $foreignCol];
                        }
                    }
                    $this->_row->{$alias}->setFromArray($row);
                }
            }
        }

        if ($result) {
            return true;
        }
        return false;
    }

    /**
     * Retorna um array com o nome das colunas da tabela.
     *
     * @return  array  Array com o nome das colunas da tabela
     */
    public function getColsNames() {
        return array_keys($this->_colsModel);
    }

    /**
     * Retorna o nome da chave prim&aacute;ria associada ao model.
     *
     * @return  string  Nome da chave prim&aacute;ria
     */
    public function getPrimary() {
        return $this->_primaria;
    }

    /**
     * Adiciona a condicao WHERE atraves do AND.
     *
     * @param   string  $cond    A condicao WHERE
     * @param   string  $val     Um valor para ser substituido na condicao WHERE
     * @return  Zend_Db_Select  Este objecto Zend_Db_Select
     */
    public function where($cond, $val = null, $type = null) {
        if (is_null($val)) $val = 0;
        return $this->_select->where($cond, $val, $type);
    }

    /**
     * Adiciona uma condicao WHERE atraves do OR.
     *
     * e identico ao where().
     *
     * @param   string  $cond    A condicao WHERE
     * @param   string  $val     Um valor para ser substituido na condicao WHERE
     * @return  Zend_Db_Select  Este objecto Zend_Db_Select
     * @see     where()
     */
    public function orWhere($cond, $val = null, $type = null) {
        return $this->_select->orWhere($cond, $val, $type);
    }

    /**
     * Conta o número de linhas resultante da query.
     *
     * @param   string  Coluna do count
     * @return  int     N&uacute;mero de linhas resultante da query
     */
    public function count($col = '*') {
        $this->_setupMetadata();
        $rowCount = clone $this->_select;
        // adiciona as campos das chaves estrangeiras
        if ($this->_loadForeigns && $col != '*') {
            $rowCount->from($this->_table, $this->_table . '.' .$col);
            foreach ($this->_foreigns as $col => $model) {
                $alias = $this->_colsModel[$col]->getParam('alias');
                $table = $tablePrefix = $this->_models[$model]->getTableName();
                $where = $this->_table . '.' . $col . '=' . $tablePrefix . '.' . $this->_models[$model]->getPrimary();
                $rowCount->joinLeft($table, $where, null);
            }
        } else {
            $rowCount->from($this->_table, $col);
        }

        if (is_array($this->_modelJoins) && count($this->_modelJoins) > 0){
            foreach ($this->_modelJoins as $method => $joins){
                foreach ($joins as $join){
                    $rowCount->{$method}($join['name'], $join['cond'], $join['cols'], $join['alias']);
                }
            }
        }
        
        //$result = $this->_db->fetchOne($rowCount);

        // limpa as configuracoes do $this->select, criando uma nova instancia
        //$rowCount = $this->_db->select();
        //return $result;*/

        //$rowCount = clone $this->_select;

        /**
         * The DISTINCT and GROUP BY queries only work when selecting one column.
         * The question is whether any RDBMS supports DISTINCT for multiple columns, without workarounds.
         */
        if (true === $rowCount->getPart(Zend_Db_Select::DISTINCT)) {
            $columnParts = $rowCount->getPart(Zend_Db_Select::COLUMNS);

            $columns = array();

            foreach ($columnParts as $part) {
                if ($part[1] == '*' || $part[1] instanceof Zend_Db_Expr) {
                    $columns[] = $part[1];
                } else {
                    $columns[] = $rowCount->getAdapter()->quoteIdentifier($part[1], true);
                }
            }

            if (count($columns) == 1 && $columns[0] == '*') {
                $groupPart = null;
            } else {
                $groupPart = implode(',', $columns);
            }
        } else {
            $groupParts = $rowCount->getPart(Zend_Db_Select::GROUP);

            foreach ($groupParts as &$part) {
                if (!($part == '*' || $part instanceof Zend_Db_Expr)) {
                    $part = $rowCount->getAdapter()->quoteIdentifier($part, true);
                }
            }

            $groupPart = implode(',', $groupParts);
        }

        $countPart  = empty($groupPart) ? 'COUNT(*)' : 'COUNT(DISTINCT ' . $groupPart . ')';
        $expression = new Zend_Db_Expr($countPart . ' AS ' . $rowCount->getAdapter()->quoteIdentifier(self::ROW_COUNT_COLUMN));

        $rowCount->__toString(); // Workaround for ZF-3719 and related
        $rowCount->reset(Zend_Db_Select::COLUMNS);
        $rowCount->reset(Zend_Db_Select::ORDER);
        $rowCount->reset(Zend_Db_Select::LIMIT_OFFSET);
        $rowCount->reset(Zend_Db_Select::GROUP);
        $rowCount->reset(Zend_Db_Select::DISTINCT);
        $rowCount->columns($expression);
        if ($this->_useCache){
            $cache = Zend_Registry::get('cache');
            if(($result = $cache->load($this->_table . '_' . md5($rowCount->__toString()))) === false) {
                $result = $this->_db->fetchOne($rowCount);
                $cache->save($result, $this->_table . '_' . md5($rowCount->__toString()), array($this->_table));
            }
        } else {
            $result = $this->_db->fetchOne($rowCount);
        }
        return $result;
    }

    /**
     * Retorna o nome da coluna a ser processado pelo __toString().
     *
     * @return  string  Nome da coluna a ser processado pelo __toString()
     */
    public function getToString() {
        if (!$this->_toString) {
            return $this->_primaria;
        }
        return $this->_toString;
    }

    /**
     * Define se carrega as chaves estrangeiras.
     *
     * @param  bool  $flag  TRUE se carrega as caches estrangeiras
     */
    public function loadForeigns($flag) {
        $this->_loadForeigns = $flag;
        $this->_checkForeigns();
    }

    /**
     * Retorna uma string com o prefixo do nome da tabela antes de cada coluna.
     *
     * @param   string  $table  Nome da tabela
     * @throws  array   $cols   Array com o nome das colunas
     * @return  string          String com o prefixo do nome da tabela antes de cada coluna
     */
    public static function getColsWithPrefix($table, $cols) {
        $colsAux = array();
        foreach ($cols as $col) {
            $colsAux[$table . '_' . $col] = $table . '.' .$col;
        }
        return $colsAux;
    }

    /**
     * Retorna uma string com o prefixo do nome da tabela antes de cada coluna.
     *
     * @param   string  $table  Nome da tabela
     * @throws  array   $cols   Array com o nome das colunas
     * @return  string          String com o prefixo do nome da tabela antes de cada coluna
     */
    protected function _getColsWithPrefix($table, $cols) {
        return self::getColsWithPrefix($table, $cols);
    }

    /**
     * Inicializa as línguas e altera a chave prim&aacute;ria de Auto para Integer(unsigned)
     */
    public function initLang() {
        if (!$this->_lang || !$this->_langs) {
            $session = Goweb::registry('session');
            $config  = Goweb::registry('config');
            $this->_lang = $session->lang;
            $this->_langs = explode(',', $config->languages->available);;
            if (!$this->_lang) $this->_lang = $this->_langs[0];
            $this->_lang = preg_replace('/([^a-z0-9_])/i', '_', $this->_lang);
            if (!$this->_lang || !$this->_langs) {
                return false;
            }
        }

        $this->_table = $this->_tablePrefix . '_' . $this->_lang;
        /**
         * as Chaves primarias devem sempre ter o auto-increment, nao interferem com o save
         * e previnem problemas com quando inserção não é feita na lingua $this->_langs[0]
         * que por vezes é mudada no config, fazendo com que existam erros e inconsistencias 
         */
        $this->{$this->_primaria} = $this->Auto();
        /*if ($this->_lang == $this->_langs[0]) {
         $this->{$this->_primaria} = $this->Auto();
         } else {
         $this->{$this->_primaria} = $this->Integer(array('unsigned' => true, 'primary' => true));
         }*/
    }

    /**
     * Define as línguas suportadas.
     *
     * @param  array  $langs  Array com os isos das línguas
     */
    public function setLangs($langs) {
        $this->_langs = (array) $langs;
    }

    /**
     * Define a língua activa.
     *
     * @param  string  $lang  línguas activa
     */
    public function setLang($lang) {
        $this->_lang = $lang;
    }

    /**
     * Atribui o nome e tabela associada ao model.
     *
     * @return  string  Nome da tabela
     */
    public function setTableName($table) {
        $this->_table = $table;
        $this->_name = $table;
    }

    /**
     * Atribui o prefixo da tabela associada ao model.
     *
     * @return  string  Nome da tabela
     */
    public function setTablePrefix($prefix) {
        $this->_tablePrefix = $prefix;
    }

    /**
     * Retorna as línguas suportadas.
     *
     * @return  array  As línguas suportadas
     */
    public function getLang() {
        return $this->_lang;
    }

    /**
     * Retorna a língua activa.
     *
     * @return  array  As línguas suportadas
     */
    public function getLangs() {
        return $this->_langs;
    }

    /**
     * Retorna o nome da tabela associada ao model.
     *
     * @return  string  Nome da tabela
     */
    public function getTableName() {
        return $this->_table;
    }

    /**
     * Define o array das colunas a pesquisar
     *
     * @param  array  $cols  Colunas a pesquisar
     */
    public function setSearchCols($cols) {
        $this->_searchCols = (array) $cols;
    }

    /**
     * Retorna o array das colunas a pesquisar
     *
     * @return  array
     */
    public function getSearchCols() {
        return $this->_searchCols;
    }

    /**
     * Define o array das colunas a sincronizar no caso de ser multilingue
     *
     * @param  array  $cols  Colunas a sincronizar
     */
    public function setSyncCols($cols) {
        $this->_syncCols = (array) $cols;
    }

    /**
     * Retorna o array das colunas a sincronizar no caso de ser multilingue
     *
     * @return  array
     */
    public function getSyncCols() {
        return $this->_syncCols;
    }

    /**
     * Retorna os dados do model em formato array.
     *
     * @return  array  Os dados do model em formato array
     */
    public function toArray() {
        return $this->_row->toArray();
    }

    /**
     * Adiciona o ORDER BY e query.
     *
     * @param   string|array  $spec  A(s) coluna(s) e a ordem
     * @return  void
     */
    public function order($spec) {
        if (! $spec instanceof Zend_Db_Expr){
            $spec = (array) $spec;
            if (isset($spec[0]) && strpos($spec[0], ', '))
            $spec = explode(', ', $spec[0]);

            foreach ($spec as $key => $col) {
                $table = $this->_table;
                $sorting = 'ASC';

                if (is_int(strpos($col, '-', 0))) {
                    $sorting = 'DESC';
                    $col = substr($col, 1);
                }

                if ($this->_loadForeigns && array_key_exists($col, $this->_foreigns)) {
                    if ($this->_colsModel[$col]->getParam('alias')) {
                        $table = $this->_colsModel[$col]->getParam('alias');
                    } else {
                        $table = $this->_models[$this->_foreigns[$col]]->getTableName();
                    }
                    $foreignCol = $this->_models[$this->_foreigns[$col]]->getToString();
                    if (!$foreignCol) {
                        $foreignCol = $this->_models[$this->_foreigns[$col]]->getPrimary();
                    }
                    $col = $foreignCol;
                }

                $spec[$key] = $table . '.' . $col . ' ' . $sorting;
            }
        }

        return $this->_select->order($spec);
    }

    /**
     * Adiciona limites e query.
     *
     * @param   int   $count   N&uacute;mero de linhas a retornar
     * @param   int   $offset  Retornar a partir destas linhas
     * @return  void
     */
    public function limit($count = null, $offset = null) {
        return $this->_select->limit($count, $offset);
    }

    /**
     * Atribui o limite e contra por número de p&aacute;gina.
     *
     * @param   int   $page      Limita os resultados por esta p&aacute;gina
     * @param   int   $rowCount  N&uacute;mero de linhas por p&aacute;gina
     * @return  void
     */
    public function limitPage($page, $rowCount) {
        return $this->_select->limitPage($page, $rowCount);
    }

    /**
     * Devolve uma c&oacute;pia do objecto Zend_Db_Select associado ao Model
     *
     * @return  Zend_Db_Select
     */
    public function getSelect() {
        return $this->_select;
    }

    /**
     * Define o objecto Zend_Db_Select do Model
     *
     * @return  Zend_Db_Select
     */
    public function setSelect($select) {
        $select->reset(Zend_Db_Select::COLUMNS);
        $select->reset(Zend_Db_Select::FROM);
        return $this->_select = $select;
    }

    /**
     * Limpa os dados do Model
     */
    public function reset() {
        $this->_select = $this->_db->select();
        $this->_row->reset();
    }

    /**
     * Devolve um objecto do tipo Goweb_Db_Model_Rowset com os resultados.
     *
     * @param array $cols Array com as colunas a retornar
     *
     * @return  bool|Goweb_Db_Model_Rowset  Retorna o objecto Goweb_Db_Rowset com os resultados ou
     *                                      FALSE se n&atilde;o foi encontrado nenhuma linha na tabela
     */
    public function fetchAll($cols = null) {
        $this->_setupMetadata();
        if (is_array($cols) && in_array('*', $cols)) {
            unset($cols[array_search('*', $cols)]);
            $cols = array_merge(array_keys($this->_colsModel), $cols);
        }
         
        $tags = array($this->_table);

        if (is_array($cols)) {
            //$extraCols = array();
            $this->_select->from($this->_table, $this->_getColsWithPrefix($this->_table, $cols));
            foreach ($cols as $key => $col) {
                if ($this->_loadForeigns &&
                ($this->_colsModel[$col]->getType() == 'ForeignKey' || $this->_colsModel[$col]->getType() == 'SelfKey')) {
                    $model = $this->_foreigns[$col];
                    $alias = $this->_colsModel[$col]->getParam('alias');
                    $table = $this->_models[$model]->getTableName();
                    /*
                     $tableAlias = ($this->_colsModel[$col]->getParam('type') == 'SelfKey') ? $alias : null;
                     $tablePrefix = ($this->_colsModel[$col]->getParam('type') == 'SelfKey') ? $alias : $table;
                     */
                    $tableAlias = $tablePrefix = ($alias ? $alias : $table);

                    $where = $this->_table . '.' . $col . '=' . $tablePrefix . '.' . $this->_models[$model]->getPrimary();
                    $foreignCols = $this->_getColsWithPrefix($tablePrefix, $this->_models[$model]->getColsNames());
                    $this->_select->joinLeft(array($tableAlias => $table), $where, $foreignCols);
                }
            }
        } else {
            $this->_select->from($this->_table, $this->_getColsWithPrefix($this->_table, array_keys($this->_colsModel)));
            if ($this->_loadForeigns) {
                // adiciona os campos das chaves estrangeiras
                foreach ($this->_foreigns as $col => $model) {
                    $alias = $this->_colsModel[$col]->getParam('alias');
                    $table = $this->_models[$model]->getTableName();
                    /*
                     $tableAlias = ($this->_colsModel[$col]->getParam('type') == 'SelfKey') ? $alias : null;
                     $tablePrefix = ($this->_colsModel[$col]->getParam('type') == 'SelfKey') ? $alias : $table;
                     */
                    $tableAlias = $tablePrefix = ($alias ? $alias : $table);

                    $where = $this->_table . '.' . $col . '=' . $tablePrefix . '.' . $this->_models[$model]->getPrimary();
                    $foreignCols = $this->_getColsWithPrefix($tablePrefix, $this->_models[$model]->getColsNames());

                    $this->_select->joinLeft(array($tableAlias => $table), $where, $foreignCols);
                    $tags [] = $table;
                }
            }
        }

        if (is_array($this->_modelJoins) && count($this->_modelJoins) > 0){
            foreach ($this->_modelJoins as $method => $joins){
                foreach ($joins as $join){
                    $this->_select->{$method}($join['name'], $join['cond'], $join['cols'], $join['alias']);
                }
            }
        }

        if ($this->_useCache){
            $cache = Zend_Registry::get('cache');
            if(($result = $cache->load($this->_table . '_' . md5($this->_select->__toString()))) === false) {
                $result = $this->_db->fetchAll($this->_select);
                $cache->save($result, $this->_table . '_' . md5($this->_select->__toString()), $tags);
            }
        } else {
            $result = $this->_db->fetchAll($this->_select);
        }

        // guarda as colunas extras submetidas
        //        $extraCols = $this->_select->getExtraCols();

        // limpa as configuracoes do $this->select, criando uma nova instancia
        $this->_select = $this->_db->select();
        $this->_modelJoins = array();

        if ($result) {
            $rowset = new Goweb_Db_Model_Rowset();

            // se sucesso, popula o Goweb_Db_Model::_rows com os valores
            foreach ($result as $r) {
                $row = new Goweb_Db_Model_Row($this->getToString());
                $row->setModelName(get_class($this));

                if (is_array($cols)) {
                    foreach ($cols as $col) {
                        /**
                         * fix para sqlserver, que limita a 32 caracteres o nome da coluna!
                         */
                        if (!array_key_exists($this->_table . '_' . $col, $r)){
                            $one = substr($this->_table . '_' . $col,0,31);
                            if (array_key_exists($one, $r))
                            $row->{$col} = $r[$one];
                            else
                            $row->{$col} = $r[substr($this->_table . '_' . $col,0,30)];
                        } else {
                            $row->{$col} = $r[$this->_table . '_' . $col];
                        }

                        if ($this->_loadForeigns && ($this->_colsModel[$col]->getType() == 'ForeignKey' || $this->_colsModel[$col]->getType() == 'SelfKey')) {
                            $model = $this->_foreigns[$col];
                            $alias = $table = $this->_models[$model]->getTableName();
                            if ($this->_colsModel[$col]->getParam('alias')) {
                                $alias = $this->_colsModel[$col]->getParam('alias');
                            }

                            $row->{$alias} = new Goweb_Db_Model_Row($this->_models[$model]->getToString());
                            $row->{$alias}->setModelName($model);

                            if ($this->_colsModel[$col]->getType() == 'SelfKey') {
                                foreach ($cols as $foreignCol) {
                                    /**
                                     * fix para sqlserver, que limita a 32 caracteres o nome da coluna!
                                     */
                                    if (!array_key_exists($alias . '_' . $foreignCol, $r)){
                                        $one = substr($alias . '_' . $foreignCol,0,31);
                                        if (array_key_exists($one, $r)){
                                            $row->{$alias}->{$foreignCol} = $r[$one];
                                        } else {
                                            $row->{$alias}->{$foreignCol} = $r[substr($alias . '_' . $foreignCol,0,30)];
                                        }
                                    } else {
                                        $row->{$alias}->{$foreignCol} = $r[$alias . '_' . $foreignCol];
                                    }
                                }
                            } else {
                                foreach (array_keys($this->_row->{$alias}->toArray()) as $foreignCol) {
                                    /**
                                     * fix para sqlserver, que limita a 32 caracteres o nome da coluna!
                                     */
                                    if (!array_key_exists($alias . '_' . $foreignCol, $r)){
                                        $one = substr($alias . '_' . $foreignCol,0,31);
                                        if (array_key_exists($one, $r)){
                                            $row->{$alias}->{$foreignCol} = $r[$one];
                                        } else {
                                            $row->{$alias}->{$foreignCol} = $r[substr($alias . '_' . $foreignCol,0,30)];
                                        }
                                    } else {
                                        $row->{$alias}->{$foreignCol} = $r[$alias . '_' . $foreignCol];
                                    }
                                }
                            }
                        }
                    }
                } else {
                    foreach (array_keys($this->_colsModel) as $col) {
                        /**
                         * fix para sqlserver, que limita a 32 caracteres o nome da coluna!
                         */
                        if (!array_key_exists($this->_table . '_' . $col, $r)){
                            $one = substr($this->_table . '_' . $col,0,31);
                            if (array_key_exists($one, $r))
                            $row->{$col} = $r[$one];
                            else
                            $row->{$col} = $r[substr($this->_table . '_' . $col,0,30)];
                        } else {
                            $row->{$col} = $r[$this->_table . '_' . $col];
                        }
                    }

                    if ($this->_loadForeigns) {
                        foreach ($this->_foreigns as $col => $model) {
                            $alias = $table = $this->_models[$model]->getTableName();
                            if ($this->_colsModel[$col]->getParam('alias')) {
                                $alias = $this->_colsModel[$col]->getParam('alias');
                            }

                            $row->{$alias} = new Goweb_Db_Model_Row($this->_models[$model]->getToString());
                            $row->{$alias}->setModelName($model);

                            foreach (array_keys($this->_row->{$alias}->toArray()) as $foreignCol) {
                                /**
                                 * fix para sqlserver, que limita a 32 caracteres o nome da coluna!
                                 */
                                if (!array_key_exists($alias . '_' . $foreignCol, $r)){
                                    $one = substr($alias . '_' . $foreignCol,0,31);
                                    if (array_key_exists($one, $r)){
                                        $row->{$alias}->{$foreignCol} = $r[$one];
                                    } else {
                                        $row->{$alias}->{$foreignCol} = $r[substr($alias . '_' . $foreignCol,0,30)];
                                    }
                                } else {
                                    $row->{$alias}->{$foreignCol} = $r[$alias . '_' . $foreignCol];
                                }
                            }
                        }
                    }
                }
                /*foreach ($extraCols as $extraCol) {
                 $row->{$extraCol} = $r[$extraCol];
                 }*/
                $rowset->add($row);
            }
            return $rowset;
        }

        return false;
    }

    /**
     * Efectua o insert ou update com dados do model, se a chave prim&aacute;ria estiver a null
     * efectua um insert, caso contr&aacute;rio um update.
     *
     * @throws  Goweb_Exception
     * @return  bool|int  Retorna um int com o valor do eltimo ID inserido se for um insert,
     *                    true se o update tiver sucesso ou false se falhar o insert/update
     */
    public function save() {
        // converte os dados para um array
        $data = $this->toArray();
        if ($this->_useCache){
            $cache = Zend_Registry::get('cache');
            $tag = array($this->_table);
        }
        // remove a coluna da chave prim&aacute;ria
        unset($data[$this->_primaria]);

        // remove as colunas estrangeiras
        foreach ($this->_foreigns as $col => $model) {
            if ($this->_colsModel[$col]->getParam('alias')) {
                unset($data[$this->_colsModel[$col]->getParam('alias')]);
            } else {
                unset($data[$this->_models[$model]->getTableName()]);
            }
            if ($this->_useCache){
                $tag[] = $this->_models[$model]->getTableName();
            }
        }

        $foreignColMultiple = array();

        // carrega os valores por default e verifica a integridade das colunas n&atilde;o nulas
        foreach ($data as $key => $value) {
            if ($key != $this->_primaria){
                if ($this->_colsModel[$key]->getParam('on_update') !== null) {
                    if (method_exists($this, $this->_colsModel[$key]->getParam('on_update'))) {
                        $data[$key] = call_user_func(array($this,  $this->_colsModel[$key]->getParam('on_update')));
                    } else {
                        $data[$key] = $this->_colsModel[$key]->getParam('on_update');
                    }
                } elseif (($this->_colsModel[$key]->getParam('on_insert') !== null) && ($this->_row->{$this->_primaria} === null)) {
                    if (method_exists($this, $this->_colsModel[$key]->getParam('on_insert'))) {
                        $data[$key] = call_user_func(array($this, $this->_colsModel[$key]->getParam('on_insert')));
                    } else {
                        $data[$key] = $this->_colsModel[$key]->getParam('on_insert');
                    }
                } elseif (($this->_colsModel[$key]->getParam('default') !== null) && ($value === null)) {
                    if (method_exists($this, $this->_colsModel[$key]->getParam('default'))) {
                        $data[$key] = call_user_func(array($this, $this->_colsModel[$key]->getParam('default')));
                    } else {
                        $data[$key] = $this->_colsModel[$key]->getParam('default');
                    }
                }

                if ($data[$key] === null) {
                    if ($this->_colsModel[$key]->getParam('notnull')) {
                        throw new Goweb_Exception('O valor da coluna "' . $key .'" n&atilde;o pode ser nulo.');
                    }
                }

                if ($this->_colsModel[$key]->getParam('unique') && !$this->isLoaded()) {
                    $this->where($this->getTableName() . ".{$key}=?", $this->{$key});
                    if ($this->count($key) > 0) {
                        throw new Goweb_Exception('O valor da coluna "' . $key .'" já existe na tabela e este devere ser único.');
                    }
                }

                if ($this->_colsModel[$key]->getType() == 'ForeignKeyMultiple'){
                    $foreignColMultiple[$key] = $data[$key];
                }
            }
        }

        // verifica de a chave prim&aacute;ria este preenchida
        if ($this->_row->{$this->_primaria} == null) {
            // n&atilde;o este, logo e um insert
            // efectua o insert, faz o refresh ao objecto e devolve o ID gerado
            $table = $this->_langs ? $this->_tablePrefix . '_' . preg_replace('/([^a-z0-9_])/i', '_', $this->_langs['0']) : $this->_table;
            if ($this->_db->insert($table, $data) > 0) {
                $this->_row->{$this->_primaria} = $this->_db->lastInsertId();
                $this->_refresh();
                $this->_addToSearch($data, preg_replace('/([^a-z0-9_])/i', '_', $this->_langs['0']));
                $data['id'] = $this->_row->{$this->_primaria};
                if (count($foreignColMultiple) > 0){
                    foreach ($foreignColMultiple as $colMultiple => $valueMultiple){
                        if ($this->_colsModel[$colMultiple]->getType() == 'ForeignKeyMultiple'){
                            $colMultiple2 = $this->_colsModel[$colMultiple]->getParam('col');
                            $modelMultipleName = $this->_colsModel[$colMultiple]->getParam('modelMultiple');
                            $modelMultiple = goweb::getModelInstance($modelMultipleName, $this->getAdapter(), false);
                            //$modelMultiple->delete
                            $modelMultiple->deleteWhere($colMultiple2.' = ' . $this->_row->{$this->_primaria}, $modelMultiple->getTableName());
                            if ($valueMultiple && count($valueMultiple) > 0) {
                                $valueMultiple = unserialize($valueMultiple);
                                foreach ($valueMultiple as $categoria_id) {
                                    if ($categoria_id){
                                        $modelMultiple->{$colMultiple2} = $this->_row->{$this->_primaria};
                                        $modelMultiple->{$colMultiple} = $categoria_id;
                                        $modelMultiple->save();
                                        $modelMultiple->reset();
                                    }
                                }
                            }
                        }

                    }
                }
                if ($this->_langs) {
                    $tables = $this->_db->listTables();
                    for ($i=1; $i<count($this->_langs); $i++) {
                        $this->_langs[$i] = preg_replace('/([^a-z0-9_])/i', '_', $this->_langs[$i]);
                        $table = $this->_tablePrefix . '_' . $this->_langs[$i];
                        $tag[] = $table;
                        if(in_array($table, $tables)){
                            $this->_db->insert($table, $data);
                            $this->_addToSearch($data, $this->_langs[$i]);
                        }
                    }
                }
                $this->_clearCache();
                return $this->_row->{$this->_primaria};
            }
        } else {
            if (count($this->_foreigns) > 0) {
                // processa as relacoes e das sincronizacoes das colunas
                $this->_processForeignKeys();
            } elseif ($this->_langs) {
                $where = $this->_db->quoteInto("{$this->_primaria} = ?", $this->_row->{$this->_primaria});
                $ndata = array();
                foreach (array_keys($data) as $col) {
                    if (in_array($col, $this->_syncCols)) {
                        $ndata[$col] = $data[$col];
                    }
                }
                if (count($ndata) > 0) {
                    $tables = $this->_db->listTables();
                    foreach ($this->_langs as $lang) {
                        if ($lang != $this->_lang) {
                            $lang = preg_replace('/([^a-z0-9_])/i', '_', $lang);
                            $table = $this->_tablePrefix . '_' . $lang;
                            $tag[] = $table;
                            if(in_array($table, $tables)){
                                $this->_db->update($table, $ndata, $where);
                                $this->_addToSearch($data, $lang, true);
                            }
                        }
                    }
                }
            }

            // a chave prim&aacute;ria este preenchida, logo e um update
            $where = $this->_db->quoteInto("{$this->_primaria} = ?", $this->_row->{$this->_primaria});
            // retorna o resultado da tentativa de actualizacao
            $result = $this->_db->update($this->_table, $data, $where);
            if (count($foreignColMultiple) > 0){
                foreach ($foreignColMultiple as $colMultiple => $valueMultiple){
                    if ($this->_colsModel[$colMultiple]->getType() == 'ForeignKeyMultiple'){
                        $colMultiple2 = $this->_colsModel[$colMultiple]->getParam('col');
                        $modelMultipleName = $this->_colsModel[$colMultiple]->getParam('modelMultiple');
                        $modelMultiple = goweb::getModelInstance($modelMultipleName, $this->getAdapter(), false);
                        //$modelMultiple->delete
                        $modelMultiple->deleteWhere($colMultiple2.' = ' . $this->_row->{$this->_primaria}, $modelMultiple->getTableName());
                        if ($valueMultiple && count($valueMultiple) > 0) {
                            $valueMultiple = unserialize($valueMultiple);
                            foreach ($valueMultiple as $categoria_id) {
                                if ($categoria_id > 0){
                                    $modelMultiple->{$colMultiple2} = $this->_row->{$this->_primaria};
                                    $modelMultiple->{$colMultiple} = $categoria_id;
                                    $modelMultiple->save();
                                    $modelMultiple->reset();
                                }
                            }
                        }
                    }

                }
            }
            $data[$this->_primaria] = $this->_row->{$this->_primaria};
            $this->_addToSearch($data, null, true);

            if (is_int($result)) {
                // se resultou, efectua um refrescamento dos dados da tabela
                $this->_clearCache();
                $this->_refresh();
                return true;
            }
        }
        $this->_clearCache();

        return false;
    }

    /**
     * Define uma relacao com outros models de 1 para N
     *
     * @param  string  $model  O nome do model
     * @param  string  $col    O nome da coluna do model
     * @param  string  $alias  O nome auxiliar do model
     */
    public function hasMany($model, $col, $alias = null) {
        $this->_relations[] = array('model' => $model,
                                    'col'   => $col,
                                    'alias' => $alias,
                                    'type'  => 'many');
    }

    /**
     * Define uma relacao com outros models de 1 para 1
     *
     * @param  string  $model  O nome do model
     * @param  string  $col    O nome da coluna do model
     * @param  string  $alias  O nome auxiliar do model
     */
    public function hasOne($model, $col, $alias = null) {
        $this->_relations[] = array('model' => $model,
                                    'col'   => $col,
                                    'alias' => $alias,
                                    'type'  => 'one');
    }

    /**
     * Retona o datetime corrente.
     *
     * @return  string  Datetime corrente
     */
    protected function _currentDatetime() {
        return date('Y-m-d H:i:s');
    }

    /**
     * Retona o date corrente.
     *
     * @return  string  Datetime corrente
     */
    protected function _currentDate() {
        return date('Y-m-d');
    }

    /**
     * Retona o ip address do utilizador.
     *
     * @return  string  Ip address do utilizador
     */
    protected function _remoteAddress() {
        return $_SERVER['REMOTE_ADDR'];
    }

    /**
     * Atribui um valor a uma coluna.
     * No caso de o $value for do tipo Goweb_Db_Model_Col acrecenta o campo ao model.
     *
     * @param   string                     $col    Nome da coluna
     * @param   string|Goweb_Db_Model_Col  $value  Valor ou tipo de coluna
     * @throws  Goweb_Exception                    Caso a coluna n&atilde;o existir no model
     *                                             ou uma tentativa de atribuir valor
     *                                             e chave prim&aacute;ria
     */
    public function __set($col, $value) {
        // se o $value for uma instancia de Goweb_Db_Model_Col
        // entco acrescenta o campo ao $_colsModel
        if ($value instanceof Goweb_Db_Model_Col) {
            if (is_null($this->_colsModel)) $this->_colsModel = array();
            $this->_cols[] = $col;
            $this->_colsModel[$col] = $value;
            $this->_row->{$col} = null;
        } else {
            if (!array_key_exists($col, $this->_colsModel)) {
                throw new Goweb_Exception('A coluna "' . $col . '" n&atilde;o existe no Model "' . get_class($this) . '"');
            } elseif ($col == $this->_primaria) {
                throw new Goweb_Exception('N&atilde;o e permitido mudar o valor da chave prim&aacute;ria');
            }
            $this->_row->{$col} = $value;
        }
    }

    /**
     * Retorna um array com o label das colunas da tabela com o aliases incluidos.
     *
     * @return  array  Array com o nome das colunas da tabela
     */
    public function getColsLabelsWithAliases() {
        $labels = array();
        foreach ($this->_colsModel as $key => $col) {
            $labels[$key] = $col->getParam('label');
        }
        foreach ($this->_foreigns as $col => $model) {
            $labels[$this->_colsModel[$col]->getParam('alias')] = $labels[$col];
        }
        return $labels;
    }

    /**
     * Processa as chaves estrangeiras, actualizando as relacoes e línguas.
     * E tambem das sincronizacoes das colunas.
     */
    protected function _processForeignKeys() {
        $selfKey = null;
        $data = array();
        foreach ($this->_foreigns as $col => $modelName) {
            $data[$col] = $this->_row->{$col};
            if ($this->_colsModel[$col]->getType() == 'SelfKey') {
                $selfKey = $col;
            }
        }

        // adiciona as colunas a sincronizar
        foreach ($this->_syncCols as $col) {
            $data[$col] = $this->_row->{$col};
        }

        if ($selfKey) {
            $this->loadForeigns(false);
            $rowset = $this->fetchAll(array($this->_primaria, $selfKey));
            $this->loadForeigns(true);
        }

        // actualiza todas as tabelas das línguas
        if ($this->_langs) {
            $where = $this->_db->quoteInto("{$this->_primaria} = ?", $this->_row->{$this->_primaria});
            foreach ($this->_langs as $lang) {
                $lang = preg_replace('/([^a-z0-9_])/i', '_', $lang);
                $this->_db->update($this->_tablePrefix . '_' . $lang, $data, $where);
            }
        }

        // se a coluna for do tipo SelfKey trata as relacoes
        if ($selfKey) {
            if ($rowset) {
                $relations = array();
                foreach ($rowset as $row) {
                    $relations[$row->{$this->_primaria}] = $row->{$selfKey};
                }
                $oldSelfKeyValue = $relations[$this->_row->{$this->_primaria}];
                if ($this->_findRelation($relations, $this->_row->{$this->_primaria}, $this->_row->{$selfKey}, count($relations))) {
                    $data = array("{$selfKey}" => $oldSelfKeyValue);
                    $where = $this->_db->quoteInto("{$selfKey} = ?", $this->_row->{$this->_primaria});
                    if ($this->_langs) {
                        foreach ($this->_langs as $lang) {
                            $lang = preg_replace('/([^a-z0-9_])/i', '_', $lang);
                            $this->_db->update($this->_tablePrefix . '_' . $lang, $data, $where);
                        }
                    } else {
                        $this->_db->update($this->_table, $data, $where);
                    }
                }
            }
        }
    }

    /**
     * Retorna se o model se encontra preenchido
     *
     * @return  bool  TRUE se o model se encontra preenchido
     */
    public function isLoaded() {
        if ($this->_row->{$this->_primaria}) {
            return true;
        }
        return false;
    }

    /**
     * Retorna o par&acirc;metro de uma coluna.
     *
     * @param   string  $col    Coluna
     * @param   string  $param  Paremetro
     * @return  mixed
     */
    public function getColParam($col, $param) {
        return $this->_colsModel[$col]->getParam($param);
    }

    /**
     * Atribui o valor a um par&acirc;metro de uma coluna.
     *
     * @param   string  $col    Coluna
     * @param   string  $param  Paremetro
     * @param   string  $value  Valor
     * @return  mixed
     */
    public function setColParam($col, $param, $value) {
        return $this->_colsModel[$col]->setParam($param, $value);
    }

    /**
     * Retorna um array com o nome das colunas da tabela com o aliases incluidos.
     *
     * @return  array  Array com o nome das colunas da tabela
     */
    public function getColsNamesWithAliases() {
        return $this->_row->getCols();
    }

    /**
     * Efectua o refrescamento dos dados da base de dados
     */
    protected function _refresh() {
        $this->find($this->_row->{$this->_primaria});
    }

    /**
     * Retorna o valor de uma coluna.
     *
     * @param   string  $col  Nome da coluna
     * @throws  Goweb_Exception  Caso a coluna n&atilde;o existir no model
     *
     * @return  O valor da coluna
     */
    public function __get($col) {
        if (!$this->_row->colExists($col)) {
            throw new Goweb_Exception('A coluna "' . $col . '" n&atilde;o existe no Model "' . get_class($this) . '"');
        }
        return $this->_row->{$col};
    }

    /**
     * Metodo chamada de metodo caso n&atilde;o exista.
     * e utilizado para instanciar dinemicamente o tipo de coluna.
     *
     * @param   $colType  string  Tipo de coluna
     * @param   $params   array   Array com os par&acirc;metros
     * @throws  Goweb_Exception   No caso do tipo de coluna n&atilde;o existir
     * @return  Goweb_Db_Model_Col
     */
    public function __call($colType, $params = array()) {
        if (!Goweb::isReadable('Goweb_Db_Model_Col_' . $colType)) {
            throw new Goweb_Exception('O tipo de coluna "Goweb_Db_Model_Col_' . $colType . ' n&atilde;o existe.');
        }

        if (count($params) > 0) {
            $params = $params[0];
        }

        if (!is_array($params)) {
            throw new Goweb_Exception('O par&acirc;metro da definicao da coluna tem de ser um array associativo');
        }

        Goweb::loadClass('Goweb_Db_Model_Col_' . $colType);
        $colClassName = 'Goweb_Db_Model_Col_' . $colType;
        return new $colClassName($params);
    }

    /**
     * Retorna o objecto Goweb_Db_Model_Col solicitado
     *
     * @return  Goweb_Db_Model_Col
     */
    public function getCol($col) {
        if (!$this->_row->colExists($col)) {
            throw new Goweb_Exception('A coluna "' . $col . '" n&atilde;o existe no Model "' . get_class($this) . '"');
        }
        return $this->_colsModel[$col];
    }

    /**
     * Limpa todas as partes definidas para o SELECT.
     *
     * @return  Zend_Db_Select  Este objecto Zend_Db_Select
     */
    public function cleanParts() {
        return $this->_select->reset();
    }

    /**
     * Retorna o array de objectos Goweb_Db_Model_Col associado ao model.
     *
     * @return array Goweb_Db_Model_Col
     */
    public function getCols() {
        return $this->_colsModel;
    }

    /**
     * Retorna o TRUE se a coluna existir no model
     *
     * @return  bool
     */
    public function colExists($col) {
        return isset($this->_colsModel[$col]);
    }

    /**
     * Remover uma coluna do model
     *
     */
    protected function _removeCol($col) {
        unset($this->_cols[array_search($col, $this->_cols)]);
        unset($this->_colsModel[$col]);
        unset($this->_row->{$col});
    }
    
    /**
     * Retorna o array com as relacoes com outros models.
     *
     * @return  array  array com as relacoes com outros models.
     */
    public function getRelations() {
        return $this->_relations;
    }

    /**
     * Retorna o SQL para a criacao da tabela.
     *
     * @return  string/array  String ou array contendo o SQL para a criacao da tabela
     */
    public function createTableSql() {
        $adapterType = 'Goweb_Db_Model_Adapter_' . ucfirst(substr(get_class($this->getAdapter()), strrpos(get_class($this->getAdapter()), '_')+1));
        Goweb::loadClass($adapterType);
        $adapter = new $adapterType();
        $sql = array();
        if ($this->getLangs()) {
            foreach ($this->getLangs() as $lang) {
                $lang = preg_replace('/([^a-z0-9_])/i', '_', $lang);
                $indexes = array();
                $this->setLang($lang);
                $this->initLang();
                $output = $adapter->getCreateStart($this->_table) . "\n";
                $cols = array();
                $indexes = array();
                foreach ($this->_colsModel as $key => $col) {
                    $method = 'get' . $col->getType();
                    $params = $col->getParams();
                    if ($params['default'] !== null) {
                        if (method_exists($this, $params['default'])) {
                            $params['default'] = null;
                        }
                    }
                    $cols[] = "\t" . $key . " " . $adapter->{$method}($params);
                    if ($col->getType() == 'ForeignKey' || $col->getType() == 'SelfKey') {
                        $indexes[] = $adapter->getCreateIndex($key, $key, $this->_table) . "\n";
                    }
                }
                $output .= implode(",\n", $cols);
                $output .=  "\n" . $adapter->getCreateEnd() . "\n";
                $sql[$this->_table] = array('sql' => $output,
                                            'indexes' => $indexes);
            }
        } else {
            $output = $adapter->getCreateStart($this->_table) . "\n";
            $cols = array();
            $indexes = array();
            foreach ($this->_colsModel as $key => $col) {
                $method = 'get' . $col->getType();
                $params = $col->getParams();
                if ($params['default'] !== null) {
                    if (method_exists($this, $params['default'])) {
                        $params['default'] = null;
                    }
                }
                $cols[] = "\t" . $key . " " . $adapter->{$method}($params);
                if ($col->getType() == 'ForeignKey') {
                    $indexes[] = $adapter->getCreateIndex($key, $key, $this->_table) . "\n";
                }
            }
            $output .= implode(",\n", $cols);
            $output .=  "\n" . $adapter->getCreateEnd() . "\n";
            $sql[$this->_table] = array('sql' => $output,
                                        'indexes' => $indexes);
        }
        return $sql;
    }

    /**
     * Retorna o valor do campo do model atribuido em Goweb_Db_Model::_toString, quando
     * mostrado o objecto.
     *
     * @return  string  Valor do campo atribuido em Goweb_Db_Model::_toString
     */
    public function __toString() {
        if (strlen($this->_toString)) {
            return (string) $this->_row->{$this->_toString};
        }
        return (string) $this->_row->{$this->_primary};
    }

    /**
     * Adiciona um tabela e colunas atraves de JOIN e query.
     *
     * @param   string           $name   O nome da tabela
     * @param   string           $cond   A condicao do JOIN
     * @param   array|string     $cols   As colunas a seleccionar da tabela
     * @param   string           $alias  O alias da tabela
     * @return  Zend_Db_Select          Este objecto Zend_Db_Select
     */
    public function join($name, $cond, $cols = null, $alias = null) {
        if (!isset($this->_modelJoins['join'])) $this->_modelJoins['join'] = array();
        $this->_modelJoins['join'][] = array('name' => $name, 'cond' => $cond, 'cols' => $cols, 'alias' =>$alias);
    }

    /**
     * Adiciona um LEFT JOIN
     *
     * @param   string           $name   O nome da tabela
     * @param   string           $cond   A condicao do JOIN
     * @param   array|string     $cols   As colunas a seleccionar da tabela
     * @param   string           $alias  O alias da tabela
     * @return  Zend_Db_Select          Este objecto Zend_Db_Select
     */
    public function joinLeft($name, $cond, $cols = null, $alias = null) {
        if (!isset($this->_modelJoins['joinLeft'])) $this->_modelJoins['joinLeft'] = array();
        $this->_modelJoins['joinLeft'][] = array('name' => $name, 'cond' => $cond, 'cols' => $cols, 'alias' =>$alias);
    }

    /**
     * Adiciona um INNER JOIN
     *
     * @param   string           $name   O nome da tabela
     * @param   string           $cond   A condicao do JOIN
     * @param   array|string     $cols   As colunas a seleccionar da tabela
     * @param   string           $alias  O alias da tabela
     * @return  Zend_Db_Select          Este objecto Zend_Db_Select
     */
    public function joinInner($name, $cond, $cols = null, $alias = null) {
        if (!isset($this->_modelJoins['joinInner'])) $this->_modelJoins['joinInner'] = array();
        $this->_modelJoins['joinInner'][] = array('name' => $name, 'cond' => $cond, 'cols' => $cols, 'alias' =>$alias);
    }

    /**
     * Define a query como um SELECT DISTINCT.
     *
     * @param   bool  $flag            e verdadeiro se for um SELECT is DISTINCT
     * @return        Zend_Db_Select  Este objecto Zend_Db_Select
     */
    public function distinct($flag = true) {
        return $this->_select->distinct($flag);
    }

    /**
     * Retorna um array com o nome das colunas estrangeiras com os aliases.
     *
     * @return  array  Array com o nome das colunas estrangeiras com os aliases.
     */
    public function getForeignColsWithAliases() {
        $cols = array();
        foreach ($this->_foreigns as $col => $model) {
            $cols[$this->_colsModel[$col]->getParam('alias')] = $col;
        }
        return $cols;
    }

    /**
     * Retorna o nome do prefixo da tabela associada ao model.
     *
     * @return  string  Nome da tabela
     */
    public function getTablePrefix() {
        return $this->_tablePrefix;
    }

    /**
     * Set's if this class should be considered as tree
     *
     * @param bool $flag
     */
    public function setIsTree($flag = true){
        $this->_isTree = (bool) $flag;
    }

    /**
     * Get's if this class should be considered as tree
     *
     * @return bool
     */
    public function getIsTree(){
        return $this->_isTree;
    }

    /**
     * Set's if results should to be cached in this class
     *
     * @param bool $flag
     */
    public function setUseCache($flag = false){
        $this->_useCache = (bool) $flag;
    }

    /**
     * Get's if results should to be cached in this class
     *
     * @return bool
     */
    public function getUseCache(){
        return $this->_useCache;
    }

    /**
     * Set's if this class should create search indexes
     *
     * @param bool $flag
     */
    public function setUseSearchIndex($flag = true){
        $this->_useSearchIndex = (bool) $flag;
    }

    /**
     * Get's if this class should create search indexes
     *
     * @return bool
     */
    public function getUseSearchIndex(){
        return $this->_useSearchIndex;
    }

    /**
     * Forçar a reconstrução da pesquisa 
     *
     */
    public function forceSearchRebuild(){
        if ($this->isLoaded()){
            $data = $this->toArray();
            if ($this->_langs){
                foreach ($this->_langs as $lang){
                    $lang = preg_replace('/([^a-z0-9_])/i', '_', $lang);
                    $select = $this->_db->select();
                    $key = $this->_tablePrefix.'_' . $lang . '.' . $this->_db->quoteIdentifier($this->_primaria);
                    $where = $this->_db->quoteInto("$key = ?", $this->_row->{$this->_primaria});

                    $select->where($where);
                    $select->from($this->_tablePrefix.'_' . $lang, $this->_getColsWithPrefix($this->_tablePrefix.'_' . $lang, array_keys($this->_colsModel)));

                    $result = $this->_db->fetchRow($select);
                    $data = array();
                    foreach ($result as $key => $item){
                        $data[str_replace($this->_tablePrefix.'_'.$lang.'_', '', $key)] = $item;
                    }

                    $this->_addToSearch($data, $lang, true);
                }
            }
        }
    }

    /**
     * Adicionar à pesquisa de frontoffice
     *
     * @param array $data array with data to include in Lucene
     * @param string $lang language to insert/update
     * @param boolean $update insert or update data
     */
    protected function _addToSearch($data, $lang = null, $update = false){
        if ($this->_useSearchIndex && Goweb::isRegistered('Goweb_Search') && $this->_link){
            if (is_null($lang)){
                $gowebSearch = Goweb::registry('Goweb_Search');
            } else {
                try {
                    $gowebSearch = new Zend_Search_Lucene(ROOT_PATH. '/cache/search/' . $lang);
                } catch (Exception $e){
                    $gowebSearch = new Zend_Search_Lucene(ROOT_PATH. '/cache/search/' . $lang, true);
                }
            }
            if ($update){
                //Zend_Search_Lucene doesn't support update, so we have to delete and re-insert
                $this->_deleteSearch($lang, $this->_row->{$this->_primaria});
            }
            $doc = new Zend_Search_Lucene_Document();
            $model = get_class($this);
            $module = explode('_', $model);
            if (isset($module[0]) && $module[0]){
                $doc->addField(Zend_Search_Lucene_Field::Keyword('Module', $module[0]));
            }
            $doc->addField(Zend_Search_Lucene_Field::Keyword('Model', get_class($this)));
            $doc->addField(Zend_Search_Lucene_Field::Keyword(get_class($this).'_'.$this->_primaria, $this->_row->{$this->_primaria}));
            preg_match_all("~{{\s*(\w*)\s*}}~s", $this->_link, $matches);
            if (isset($matches[1]) && count($matches[1]) > 0){
                $link = $this->_link;
                foreach ($matches[1] as $col){
                    if ($col == $this->_primaria){
                        $link = str_replace('{{' . $col .'}}', $this->_row->{$this->_primaria}, $link);
                    } else if (isset($data[$col])){
                        $link = str_replace('{{' . $col .'}}', $data[$col], $link);
                    }
                }
                if (strcmp($this->_link, $link) != 0){
                    $doc->addField(Zend_Search_Lucene_Field::Keyword('link', $link));
                }
            }

            foreach ($this->getSearchCols() as $searchCol){
                if (!array_key_exists($searchCol, $data)){
                    throw new Goweb_Exception('A coluna "' . $searchCol . '" n&atilde;o existe no Model "' . get_class($this) . '"');
                }
                if (strlen($data[$searchCol]) > 0) {
                    if ($this->_colsModel[$searchCol]->getType() == 'File' && $data[$searchCol]){
                        $uploadPath = WWW_PATH . DIRECTORY_SEPARATOR  . trim($this->getCol($searchCol)->getParam('target'), '/') . DIRECTORY_SEPARATOR;
                        switch (pathinfo($data[$searchCol], PATHINFO_EXTENSION)) {
                        	case 'docx':
                                goweb::loadClass('Zend_Search_Lucene_Document_Docx');
                                $docFile = Zend_Search_Lucene_Document_Docx::loadDocxFile($uploadPath . $data[$searchCol]);
                                $doc->addField(Zend_Search_Lucene_Field::Text($searchCol, $docFile->body));
                        	break;
                            case 'xlsx':
                                goweb::loadClass('Zend_Search_Lucene_Document_Xlsx');
                                $docFile = Zend_Search_Lucene_Document_Xlsx::loadXlsxFile($uploadPath . $data[$searchCol]);
                                $doc->addField(Zend_Search_Lucene_Field::Text($searchCol, $docFile->body));
                            break;
                            case 'doc':
                                goweb::loadClass('Zend_Search_Lucene_Document_Doc');
                                $docFile = Zend_Search_Lucene_Document_Doc::loadDocFile($uploadPath . $data[$searchCol]);
                                $doc->addField(Zend_Search_Lucene_Field::Text($searchCol, $docFile->body));
                            break;
                            case 'pdf':
                                goweb::loadClass('Zend_Search_Lucene_Document_Pdf');
                                $docFile = Zend_Search_Lucene_Document_Pdf::loadPdfFile($uploadPath . $data[$searchCol]);
                                $doc->addField(Zend_Search_Lucene_Field::Text($searchCol, $docFile->body));
                            break;
                            
                        	default:
                        		throw new Goweb_Exception('A extens&atilde;o "' . pathinfo($data[$searchCol], PATHINFO_EXTENSION) . '" n&atilde;o &eacute; suportada para a pesquisa, no Model "' . get_class($this) . '"');
                        	break;
                        }
                    } else {
                        $doc->addField(Zend_Search_Lucene_Field::Text($searchCol, $data[$searchCol], CHARSET));
                    }
                }
            }
            $gowebSearch->addDocument($doc);
            $gowebSearch->commit();
        }
    }
    
    protected function _deleteSearch($lang, $primary){
        if (is_null($lang)){
            $lang = preg_replace('/([^a-z0-9_])/i', '_', $this->_lang);
        }
        try {
            $gowebSearch = new Zend_Search_Lucene(ROOT_PATH. '/cache/search/' . $lang);
        } catch (Exception $e){
            $gowebSearch = new Zend_Search_Lucene(ROOT_PATH. '/cache/search/' . $lang, true);
        }
        //Zend_Search_Lucene doesn't support update, so we have to delete and re-insert
        $query = new Zend_Search_Lucene_Search_Query_MultiTerm();
        $query->addTerm(new Zend_Search_Lucene_Index_Term($primary, get_class($this).'_'.$this->_primaria), true);
        $hits = $gowebSearch->find($query);
        foreach ($hits as $hit) {
            $gowebSearch->delete($hit->id);
        }
    }
    
    /**
     * Define as colunas a serem mostradas numa nova tab.
     *
     * @param string $nameCol
     * @param array $listCols
     * @throws  Goweb_Exception   O par&acirc;metro submetido n&atilde;o &eacute; v&aacute;lido
     * @throws  Goweb_Exception   J&aacute; existe uma tab com o mesmo nome
     */
    public function addTabCols($nameCol = null, $listCols = array()) {
        if (!is_array($listCols) || count($listCols) == 0) {
            throw new Goweb_Exception('O par&acirc;metro submetido em "Goweb_Db_Model::addTabCols()" n&atilde;o &eacute; v&aacute;lido');
        }

        if (array_key_exists($nameCol, $this->_tabs)){
            throw new Goweb_Exception('J&aacute; existe uma tab com o nome "' . $nameCol . '" no Model "' . get_class($this) . '"');
        }

        foreach ($listCols as $col){
            if (!array_key_exists($col, $this->_colsModel)) {
                throw new Goweb_Exception("A coluna '$col' n&atilde;o existe no model '" . get_class($this) ."'.");
            }
        }

        $this->_tabs[$nameCol] = $listCols;
    }

    /**
     * Retorna o array com as colunas associadas a cada tab.
     *
     * @return  array
     */
    public function getTabCols() {
        return $this->_tabs;
    }

    /**
     * Obter os filhos de uma categoria
     *
     * @param int $paiId
     * @param string $paiIdCol
     * @return array|object
     */
    public function getFilhos($paiId, $paiIdCol = 'pai_id') {
        $this->reset();
        $this->where($paiIdCol.' = ?', $paiId);
        return $this->fetchAll();
    }

    /**
     * Obter o número de filhos de uma categoria
     *
     * @param int $paiId
     * @param string $paiIdCol
     * @return array|object
     */
    public function getCountFilhos($paiId, $paiIdCol = 'pai_id') {
        $this->reset();
        $this->where($paiIdCol.' = ?', $paiId);
        return $this->count();
    }

    /**
     * Obter todos os ids dos filhos de uma categoria
     *
     * @param int $categoria_id
     * @param string $paiIdCol
     * @param boolean $recursive
     * @return array
     */
    public function getFilhosIds($categoria_id, $paiIdCol = 'pai_id', $recursive = false) {
        $categories = $this->getFilhos($categoria_id, $paiIdCol);
        $cats = array();
        if ($categories){
            foreach ($categories as $categoria_id) {
                $cats[] = $categoria_id->{$this->getPrimary()};
                if (true === $recursive) {
                    $cats = array_merge($cats, $this->getFilhosIds($categoria_id->{$this->getPrimary()}, $paiIdCol, true));
                }
            }
        }
        return $cats;
    }

    /**
     * Obter todos os pais de uma determinada categoria
     *
     * @param int $categoria_id
     * @param string $paiIdCol
     * @return array
     */
    public function getPais($categoria_id = 0, $paiIdCol = 'pai_id'){
        $cats = array($categoria_id);
        if (0 === $categoria_id) {
            return $cats;
        }
        if ($this->find($categoria_id)){
            if (0 !== $this->{$paiIdCol}) {
                $cats = array_merge($cats, $this->getPais($this->{$paiIdCol}, $paiIdCol));
            }
        }
        return $cats;
    }

    /**
     * Get actions of a Controller
     *
     * @param String $file full path to the filename
     * @return array with actions of a Controller
     */
    protected function _getClassActions($file){
        if (!file_exists($file)) return array();
        $arr_methods = array();
        $arr = file($file);
        foreach ($arr as $line)
        {
            if (preg_match('/function ([_A-Za-z0-9]+)/', $line, $regs)){
                /**
                 * ignorar metodos começados por _ e que não seja Action
                 */
                if (substr($regs[1],0,1) !== '_' && substr($regs[1],-6) === 'Action')
                $arr_methods[] = substr($regs[1],0,-6);
            }
        }
        return $arr_methods;
    }

    /**
     * Obter Caracteres especiais usados em windows
     *
     * @return array
     */
    protected function _get_html_translation_table_CP1252() {
        $trans = get_html_translation_table(HTML_ENTITIES);
        $trans[chr(130)] = '&sbquo;';    // Single Low-9 Quotation Mark
        $trans[chr(131)] = '&fnof;';    // Latin Small Letter F With Hook
        $trans[chr(132)] = '&bdquo;';    // Double Low-9 Quotation Mark
        $trans[chr(133)] = '&hellip;';    // Horizontal Ellipsis
        $trans[chr(134)] = '&dagger;';    // Dagger
        $trans[chr(135)] = '&Dagger;';    // Double Dagger
        $trans[chr(136)] = '&circ;';    // Modifier Letter Circumflex Accent
        $trans[chr(137)] = '&permil;';    // Per Mille Sign
        $trans[chr(138)] = '&Scaron;';    // Latin Capital Letter S With Caron
        $trans[chr(139)] = '&lsaquo;';    // Single Left-Pointing Angle Quotation Mark
        $trans[chr(140)] = '&OElig;    ';    // Latin Capital Ligature OE
        $trans[chr(145)] = '&lsquo;';    // Left Single Quotation Mark
        $trans[chr(146)] = '&rsquo;';    // Right Single Quotation Mark
        $trans[chr(147)] = '&ldquo;';    // Left Double Quotation Mark
        $trans[chr(148)] = '&rdquo;';    // Right Double Quotation Mark
        $trans[chr(149)] = '&bull;';    // Bullet
        $trans[chr(150)] = '&ndash;';    // En Dash
        $trans[chr(151)] = '&mdash;';    // Em Dash
        $trans[chr(152)] = '&tilde;';    // Small Tilde
        $trans[chr(153)] = '&trade;';    // Trade Mark Sign
        $trans[chr(154)] = '&scaron;';    // Latin Small Letter S With Caron
        $trans[chr(155)] = '&rsaquo;';    // Single Right-Pointing Angle Quotation Mark
        $trans[chr(156)] = '&oelig;';    // Latin Small Ligature OE
        $trans[chr(159)] = '&Yuml;';    // Latin Capital Letter Y With Diaeresis
        ksort($trans);
        return $trans;
    }

    /**
     * Limpar a string de caracteres estranhos, especialmente em ficheiros 
     *
     * @param String $sString
     * @return String
     */
    protected function _clearString($sString) {
        $string = strtr(utf8_decode($sString), $this->_get_html_translation_table_CP1252());
        $string = preg_replace('/\&(.)[^;]*;/', '\\1', $string);
        $string = preg_replace('/[ ]/', '-', $string);
        $string = strtolower($string);
        $string = preg_replace('/([^a-z0-9-]+)/', '-', $string);
        //$string = preg_replace('{(-)\1+}', '$1', $string);
        if (substr($string, -1) == '-'){
            $string = substr($string, 0, -1);
        }
        return $string;
    }

    /**
     * limpar a cache
     *
     */
    protected function _clearCache(){
        if ($this->_useCache){
            $cache = Zend_Registry::get('cache');
            $cache->clean(Zend_Cache::CLEANING_MODE_ALL);
            clearstatcache();
        }
    }
}