<?php
/**
 * Provides you the ability to use Doctrine as a source
 * with the Grid.
 *
 * @package   Bvb_Grid
 * @author James Solomon < [email protected]>
 */
class Bvb_Grid_Source_Doctrine
    extends Bvb_Grid_Source_Db_DbAbstract
        implements Bvb_Grid_Source_SourceInterface
{
    protected $_totalRecords;
    /**
     * Stores the supplied Doctrine_Query
     *
     * @var Doctrine_Query
     */
    protected $_query;
    /**
     * Stores the parsed out DQL
     *
     * @var array
     */
    protected $_queryParts = array(
        'select' => array(),
        'from'   => array(),
        'join'   => array()
    );
    /**
     * Intialize the Doctrine_Query. We will parse out
     * all the provided DQL or start a Doctrine_Query
     * if an instance of Doctrine_Record is provided
     *
     * @param mixed $q
     */
    public function __construct($q)
    {
        if ($q instanceof Doctrine_Record) {
            $q = get_class($q);
        }
        if (is_string($q)) {
            $q = Doctrine_Query::create()->from($q);
        }
        if (!$q instanceof Doctrine_Query) {
            require_once 'Bvb/Grid/Source/Doctrine/Exception.php';
            throw new Bvb_Grid_Source_Doctrine_Exception(
                "Please provide only an instance of Doctrine_Query "
                 . "or a valid Doctrine_Record instance"
            );
        }
        $this->_query = $q;
        $this->_setFromParts(); // called here
        $this->_setSelectParts();
    }
    /**
     * Simple method for the Grid to determine if this
     * Source can handle CRUD
     *
     * @return boolean
     */
    public function hasCrud()
    {
        return true;
    }
    /**
     * Returns the "main" table
     * the one after select * FROM {MAIN_TABLE}
     *
     * @return array
     */
    public function getMainTable()
    {
        $table = Doctrine::getTable($this->_queryParts['from']['tableModel']);
        return array('table' => $table->getTableName());
    }
    /**
     * builds a key=>value array
     *
     * they must have two options
     * title and field
     * field is used to perform queries.
     * Must have table name or table alias as a prefix
     * ex: user.id | country.population
     *
     * The key for this array is the output field
     * If raw sql is somehting like
     *
     * select name as alias, country from users
     *
     * the return array must be like this:
     *
     * array('alias'=>array('title'=>'alias','field'=>'users.name'));
     *
     * its not bad idea to apply this to fields titles
     * $title = ucwords(str_replace('_',' ',$title));
     *
     * @return array
     */
    public function buildFields()
    {
        $this->_setSelectParts();
        return $this->_queryParts['select'];
    }
    /**
     * Use the supplied Doctrine_Query to find its primary ID
     *
     * @return array Primary Keys for specified table
     */
    public function getIdentifierColumns($table = null)
    {
        $return = array();
        $table = Doctrine::getTable($this->_getModelFromTable($table));
        $alias = $this->_queryParts['from']['alias'];
        //Get the Primary Key(s) for provided table
        $ids = $table->getIdentifierColumnNames();
        //Append the alias to each Primary key field
        foreach ($ids as $id) {
            $return[] = (!empty($alias)) ? $alias . '.' . $id : $id;
        }
        return $return;
    }
    /**
     * Gets a unique record as a associative array
     *
     * @param $table
     * @param $condition
     * @return array
     */
    public function getRecord($table, array $condition)
    {
        $tableModel = $this->_getModelFromTable($table);
        $query = Doctrine_Query::create()->from($tableModel);
        foreach ($condition as $field => $value) {
            $query->addWhere($field . ' = ?', $value);
        }
        $results = $query->fetchArray(array(), Doctrine::HYDRATE_SCALAR);
        $newResults = $this->_cleanQueryResults($results);
        if (count($newResults) == 1) {
            return $newResults[0];
        }
        return $newResults;
    }
    /**
     * Should return the database server name or source name
     *
     * Ex: mysql, pgsql, array, xml
     *
     * @return string
     */
    public function getSourceName()
    {
        return strtolower($this->_query->getConnection()->getDriverName());
    }
    /**
     * Runs the query and returns the result as a associative array
     *
     * @return array
     */
    public function execute()
    {
        $newQuery = clone $this->_query;
        $results = $newQuery->execute(array(), Doctrine::HYDRATE_SCALAR);
        return $this->_cleanQueryResults($results);
    }
    /**
     * Get a record detail based the current query
     *
     * <code>
     * $where = array(
     *     array('columnName' => 'searchValue')
     * )
     * </code>
     *
     * @param array $where
     * @return array
     */
    public function fetchDetail(array $where)
    {
        /**
         * Remove these since we are trying to retrieve
         * a specific row
         */
        $this->_query->removeDqlQueryPart('limit')
                     ->removeDqlQueryPart('offset');
        foreach ($where as $column => $value) {
            $this->_query->addWhere($column . ' = ?', $value);
        }
        return $this->execute();
    }
    /**
     * Return the total of records
     *
     * @return integer
     */
    public function getTotalRecords()
    {
        if($this->_totalRecords>0)
            return $this->_totalRecords;
        return (int) $this->_query->count();
    }
    /**
     * Ex: array('c'=>array('tableName'=>'Country'));
     * where c is the table alias. If the table as no alias,
     * c should be the table name
     *
     * TODO : Find where this is used, and test
     *
     * @return array
     */
    public function getTableList()
    {
        $return = array();
        $fromAlias = (empty($this->_queryParts['from']['alias'])) ? $this->_queryParts['from']['tableName'] : $this->_queryParts['from']['alias'];
        $fromName = $this->_queryParts['from']['tableName'];
        $return[$fromAlias] = array('tableName' => $fromName);
        foreach ($this->_queryParts['join'] as $joinTypes) {
            foreach ($joinTypes as $join) {
                $return[$join['alias']] = array('tableName' => $join['tableAlias']);
            }
        }
        return $return;
    }
    /**
     * Return possible filters values based on field definion
     * This is mostly used for enum fields where the possibile
     * values are extracted
     *
     * Ex: enum('Yes','No','Empty');
     *
     * should return
     *
     * array('Yes'=>'Yes','No'=>'No','Empty'=>'Empty');
     *
     * @param $field
     * @return string
     */
    public function getFilterValuesBasedOnFieldDefinition($field)
    {
        if (strpos($field, '(') !== false) {
            return 'text';
        }
        $table = $this->_getModelFromColumn($field);
        $tableClass = Doctrine::getTable($table);
        if (strpos($field, '.') !== false) {
            list($alias, $column) = explode('.', $field);
        } else {
            $column = $field;
        }
        $definition = $tableClass->getDefinitionOf($column);
        if ($definition['type'] == 'enum') {
            foreach ($definition['values'] as $val) {
                $return[$val] = $val;
            }
            return $return;
        }
        return 'text';
    }
    /**
     * Return te field type
     * char, varchar, int
     *
     * Note: If the field is enum or set,
     * the value returned must be set or enum,
     * and not the full definition
     *
     * @param string $field
     * @return string|null
     */
    public function getFieldType($field)
    {
        if (!isset($this->_queryParts['select'][$field]['field']))
            return null;
        if (strpos($this->_queryParts['select'][$field]['field'], '.') !== false) {
            list($alias, $fieldName) = explode('.', $this->_queryParts['select'][$field]['field']);
        } else {
            $fieldName = $field;
        }
        $tableModel = $this->_getModelFromColumn($field);
        return Doctrine::getTable($tableModel)->getTypeOfColumn($fieldName);
    }
    /**
     * Build the order part from the query.
     *
     * The first arg is the field to be ordered and the $order
     * arg is the correspondent order (ASC|DESC)
     *
     * If the $reset is set to true, all previous order should be removed
     *
     * @param string $field
     * @param string $order
     * @param bool $reset
     * @return Bvb_Grid_Source_Doctrine
     */
    public function buildQueryOrder($field, $order, $reset = false)
    {
        if ($reset) {
            $this->_query->removeDqlQueryPart('orderby');
        }
        $this->_query->addOrderBy($field . ' ' . $order);
        return $this;
    }
    /**
     * Build the query limit clause
     *
     * @param $start
     * @param $offset
     * @return Bvb_Grid_Source_Doctrine
     */
    public function buildQueryLimit($start, $offset)
    {
        $this->_query->limit($start)->offset($offset);
        return $this;
    }
    /**
     * Returns the select object
     *
     * @return Doctrine_Query
     */
    public function getSelectObject()
    {
        return $this->_query;
    }
    /**
     * Returns the selected order
     * that was defined by the user in the query entered
     * and not the one generated by the system
     *
     * If empty an empty array must be returned.
     *
     * Else the array must be like this:
     *
     * <code>
     * $return = array(
     *     0 => field
     *     1 => (ASC|DESC)
     * );
     * </code>
     *
     * @return array
     */
    public function getSelectOrder()
    {
        $orderBy = $this->_query->getDqlPart('orderby');
        if (empty($orderBy))
            return array();
        $result = array();
        foreach ($orderBy as $anOrderby) {
            $orderBys = explode(',', $anOrderby);
            foreach ($orderBys as $order) {
                $parts = explode(' ', trim($order));
                if (isset($parts[1]) && !in_array(strtolower($parts[1]), array('desc', 'asc'))) {
                    $parts[1] = '';
                }
                $result[] = implode(' ', $parts);
            }
        }
        /**
         * FIX : #215
         *
         * This function seems to be needed to only return the
         * first order, even if more than one is present
         *
         * @see Bvb_Grid_Source_Zend_Select::getSelectOrder()
         */
        return $result[0];
    }
    /**
     * Should preform a query based on the provided by the user
     * select the two fields and return an array $field=>$value
     * as result
     *
     * ex: SELECT $field, $value FROM *
     * array('1'=>'Something','2'=>'Number','3'=>'history')....;
     *
     * @param string $field
     * @param string $value
     * @return array
     */
    public function getDistinctValuesForFilters($field, $fieldValue, $order = 'name ASC')
    {
        $return = array();
        $newQuery = clone $this->_query;
        $distinct = new Doctrine_Expression("DISTINCT($field)");
        $newQuery->removeDqlQueryPart('select')
                 ->removeDqlQueryPart('orderby')
                 ->removeDqlQueryPart('limit')
                 ->removeDqlQueryPart('offset')
                 ->select("$distinct AS field, $fieldValue AS value")
                 ->orderBy('value ASC');
        //Only using Scalar here, b/c of an aparent bug with Doctrine::HYDRATE_ARRAY
        $results = $newQuery->execute(array(), Doctrine::HYDRATE_SCALAR);
        $cleanResults = array();
        foreach ($results as $result) {
            $temp = array();
            foreach ($result as $column => $value) {
                $pos = strpos($column, '_');
                $field = substr($column, ++$pos);
                $temp[$field] = $value;
            }
            $cleanResults[] = $temp;
        }
        foreach ($cleanResults as $value) {
            $return[$value['field']] = $value['value'];
        }
        return $return;
    }
    /**
     * Perform a sqlexp
     *
     * $value =  array ('functions' => array ('AVG'), 'value' => 'Population' );
     *
     * Should be converted to
     * SELECT AVG(Population) FROM *
     *
     * $value =  array ('functions' => array ('SUM','AVG'), 'value' => 'Population' );
     *
     * Should be converted to
     * SELECT SUM(AVG(Population)) FROM *
     *
     * @param array $value
     */
    public function getSqlExp(array $value, $where = array())
    {
        $return = array();
        $newQuery = clone $this->_query;
        foreach (array_reverse($value['functions']) as $key => $func) {
            if ($key == 0) {
                $exp = $func . '(' . $value['value'] . ')';
            } else {
                $exp = $func . '(' . $exp . ')';
            }
        }
        $exp = new Doctrine_Expression($exp);
        $newQuery->removeDqlQueryPart('select')
                 ->removeDqlQueryPart('orderby')
                 ->removeDqlQueryPart('limit')
                 ->removeDqlQueryPart('offset')
                 ->select("$exp AS total");
        return $newQuery->fetchOne(array(), Doctrine::HYDRATE_SINGLE_SCALAR);
    }
    /**
     * Adds a fulltext search instead of a addcondition method
     *
     * $field has an index search
     * $field['search'] = array('extra'=>'boolean|queryExpansion','indexes'=>'string|array');
     *
     * if no indexes provided, use the field name
     *
     * boolean =>  IN BOOLEAN MODE
     * queryExpansion =>  WITH QUERY EXPANSION
     *
     * @param $filter
     * @param $field
     */
    public function addFullTextSearch($filter, $field)
    {
        throw new Bvb_Grid_Source_Doctrine_Exception("Fulltext searching is currently not supported by the Doctrine Source");
    }
    /**
     * Adds a new condition to the current query
     * $filter is the value to be filtered
     * $op is the opreand to be used: =,>=, like, llike,REGEX,
     * $completeField. use the index $completField['field'] to
     * specify the field, to avoid ambiguous
     *
     * @param $filter
     * @param $op
     * @param $completeField
     * @return Bvb_Grid_Source_Doctrine
     */
    public function addCondition($filter, $op, $completeField)
    {
        $field = $completeField['field'];
        /**
         * FIX : #218
         * We need to make sure to use HAVING when there is function
         * in the select, as you cannot use these selected fields in the
         * WHERE clause, and all others will use the WHERE clause
         */
        $func = 'addWhere';
        if (strpos($field, '(') !== false) {
            $func = 'addHaving';
        }
        switch (strtolower($op)) {
            case 'sqlexp':
                $this->_query->$func($filter);
                break;
            case 'isnull':
                $this->_query->$func($field.' IS NULL ');
                break;
            case 'isnnotull':
                $this->_query->$func($field.' IS NOT NULL ');
                break;
            case 'empty':
                $this->_query->$func($field . " =''");
                break;
            case 'equal':
            case '=':
                $this->_query->$func($field . ' = ?', $filter);
                break;
            case 'regex':
                $this->_query->$func($field . " REGEXP ?", $filter);
                break;
            case 'rlike':
                $this->_query->$func($field . " LIKE ?", $filter . "%");
                break;
            case 'llike':
                $this->_query->$func($field . " LIKE ?", "%" . $filter);
                break;
            case '>=':
                $this->_query->$func($field . " >= ?", $filter);
                break;
            case '>':
                $this->_query->$func($field . " > ?", $filter);
                break;
            case '<>':
            case '!=':
                $this->_query->$func($field . " <> ?", $filter);
                break;
            case '<=':
                $this->_query->$func($field . " <= ?", $filter);
                break;
            case '<':
                $this->_query->$func($field . " < ?", $filter);
                break;
            case 'in':
                $filter = explode(',', $filter);
                $this->_query->whereIn($field, $filter);
                break;
            case '&':
            case 'and':
            case 'AND':
            case 'flag':
            case 'FLAG':
                $this->_query->$func($field . " & ? <> 0", $filter);
                break;
            case 'range':
                $start = substr($filter, 0, strpos($filter, '<>'));
                $end = substr($filter, strpos($filter, '<>') + 2);
                $this->_query->$func($field . " between ? and ?", array($start, $end));
                break;
            case '||':
                $this->_query->orWhere($field . " LIKE ?", "%" . $filter . "%");
                break;case 'like':
            default:
                $this->_query->$func($field . " LIKE ?", "%" . $filter . "%");
                break;
        }
        return $this;
    }
    /**
     * Insert an array of key=>values in the specified table
     *
     * @param string $table
     * @param array $post
     * @return boolean
     */
    public function insert($table, array $post)
    {
        $tableModel = $this->_getModelFromTable($table);
        /**
         * @var Doctrine_Record
         */
        $model = new $tableModel;
        $model->fromArray($post);
        $return = $model->trySave();
        return $return;
    }
    /**
     * Update values in a table using the $condition clause
     *
     * The condition clause is a $field=>$value array
     * that should be escaped by YOU (if your class doesn't do that for you)
     * and usinf the AND operand
     *
     * Ex: array('user_id'=>'1','id_site'=>'12');
     *
     * Raw SQL: * WHERE user_id='1' AND id_site='12'
     *
     * @param string $table
     * @param array $post
     * @param array $condition
     * @return integer Of Affected rows
     */
    public function update($table, array $post, array $condition)
    {
        $tableModel = $this->_getModelFromTable($table);
        $query = Doctrine_Query::create()->update($tableModel);
        foreach ($post as $field => $value) {
            $query->set($field, '?', $value);
        }
        foreach ($condition as $field => $value) {
            $query->addWhere($field . ' = ?', $value);
        }
       return  $query->execute();
    }
    /**
     * Delete a record from a table
     *
     * The condition clause is a $field=>$value array
     * that should be escaped by YOU (if your class doesn't do that for you)
     * and usinf the AND operand
     *
     * Ex: array('user_id'=>'1','id_site'=>'12');
     * Raw SQL: * WHERE user_id='1' AND id_site='12'
     *
     * @param string $table
     * @param array $condition
     * @return integer Of Affected rows
     */
    public function delete($table, array $condition)
    {
        $tableModel = $this->_getModelFromTable($table);
        $query = Doctrine_Query::create()->delete($tableModel);
        foreach ($condition as $field => $value) {
            $query->addWhere($field . ' = ?', $value);
        }
        return $query->execute();
    }
    /**
     * @todo Change to Doctrine Native function
     * @see Bvb_Grid_Source_SourceInterface::quoteValue()
     */
    public function quoteValue ($value)
    {
        return mysql_real_escape_string($value);
    }
    /**
     * Removes any order in query
     *
     * @return Bvb_Grid_Source_Doctrine
     */
    public function resetOrder()
    {
        $this->_query->removeDqlQueryPart('orderby');
        return $this;
    }
    /**
     * Removes any limit in query
     *
     * @return Bvb_Grid_Source_Doctrine
     */
    public function resetLimit()
    {
        $this->_query->removeDqlQueryPart('limit');
        $this->_query->removeDqlQueryPart('offset');
        return $this;
    }
    /**
     * Cache handler.
     *
     * TODO: Research what 'cache' does, might just need to look at the
     *       bool and see if we need to set Doctrine Cache or not
     */
    public function setCache($cache)
    {
        //die(Zend_Debug::dump($cache));
        //die('setCache');
    }
    /**
     * Build the form based on a Model or query
     *
     * @return array
     */
    public function buildForm($inputsType = array())
    {
        $table = $this->_queryParts['from']['tableModel'];
        $columns = Doctrine::getTable($table)->getColumns();
        return $this->buildFormElements($columns, array(), $inputsType);
    }
    /**
     * Will build out an array of form elements,
     * based on the column type and return the array
     * to be used when loading the Bvb_Grid_Form
     *
     * @param array $cols
     * @param array $info
     * @return array
     */
    public function buildFormElements(array $cols, $info = array(), $inputsType= array())
    {
        $form = array();
        foreach ($cols as $column => $detail) {
            if (isset($detail['primary']) && $detail['primary']) {
                continue;
            }
            $label = ucwords(str_replace('_', ' ', $column));
            switch ($detail['type']) {
                case 'enum':
                    $form['elements'][$column] = array('select', array('multiOptions' => $detail['values'], 'required' => (array_key_exists('notnull', $detail)) ? true : false, 'label' => $label));
                    break;
                case 'string':
                case 'varchar':
                case 'char':
                    $length = $detail['length'];
                    $form['elements'][$column] = array('text', array('validators' => array(array('stringLength', false, array(0, $length))), 'size' => 40, 'label' => $label, 'required' =>  (array_key_exists('notnull', $detail)) ? true : false, 'value' => (! empty($detail['default']) ? $detail['default'] : "")));
                    break;
                case 'date':
                    $form['elements'][$column] = array('text', array('validators' => array(array('Date')), 'size' => 10, 'label' => $label, 'required' =>  (array_key_exists('notnull', $detail)) ? true : false, 'value' => (! empty($detail['default']) ? $detail['default'] : "")));
                    break;
                case 'time':
                    $form['elements'][$column] = array('text', array('validators' => array(array(new Zend_Validate_Date('H:i:s'))), 'size' => 19, 'label' => $label, 'required' => (array_key_exists('notnull', $detail)) ? true : false, 'value' => (! empty($detail['default']) ? $detail['default'] : "")));
                    break;
                case 'datetime':
                case 'timestamp':
                    $form['elements'][$column] = array('text', array('validators' => array(array(new Zend_Validate_Date('Y-m-d H:i:s'))), 'size' => 19, 'label' => $label, 'required' =>  (array_key_exists('notnull', $detail)) ? true : false, 'value' => (! empty($detail['default']) ? $detail['default'] : "")));
                    break;
                case 'text':
                case 'mediumtext':
                case 'longtext':
                case 'smalltext':
                    $form['elements'][$column] = array('textarea', array('label' => $label, 'required' =>  (array_key_exists('notnull', $detail)) ? true : false, 'filters' => array('StripTags')));
                    break;
                case 'integer':
                case 'int':
                case 'bigint':
                case 'mediumint':
                case 'smallint':
                case 'tinyint':
                    $isZero = (! empty($detail['default']) && $detail['default'] == "0") ? true : false;
                    $form['elements'][$column] = array('text', array('validators' => array('Digits'), 'label' => $label, 'size' => 10, 'required' => ($isZero == false && (array_key_exists('notnull', $detail))) ? false : true, 'value' => (! empty($detail['default']) ? $detail['default'] : "")));
                    break;
                case 'float':
                case 'decimal':
                case 'double':
                    $form['elements'][$column] = array('text', array('validators' => array('Float'), 'size' => 10, 'label' => $label, 'required' =>  (array_key_exists('notnull', $detail)) ? true : false, 'value' => (! empty($detail['default']) ? $detail['default'] : "")));
                    break;
                default:
                    break;
            }
        }
        foreach ( $inputsType as $field => $type ) {
            $form['elements'][$field][0] = strtolower($type);
        }
        return $form;
    }
    /**
     * Used within this class to clean the hydrated result
     * to be something more Grid friendly
     *
     * @param array $results
     * @return array Cleaned results
     */
    protected function _cleanQueryResults($results)
    {
        $newArray = array();
        if (empty($this->_queryParts['from']['alias'])) {
            foreach ($results as $rows) {
                $temp = array();
                foreach ($rows as $col => $val) {
                    $name = str_replace($this->_queryParts['from']['tableModel'] . '_', '', $col);
                    $temp[$name] = $val;
                }
                $newArray[] = $temp;
            }
        } else {
            foreach ($results as $rows) {
                $temp = array();
                foreach ($rows as $col => $val) {
                    $parts = explode('_', $col, 2);
                    $field = implode('.', $parts);
                    foreach ($this->_queryParts['select'] as $alias => $select) {
                        if ($field == $select['field'] || $parts[1] == $alias) {
                            $temp[$alias] = $val;
                        }
                    }
                }
                $newArray[] = $temp;
            }
        }
        return $newArray;
    }
    /**
     * Used to parse out the SELECT pieces of the DQL
     * and place it in the $_queryParts array for use
     * in many other places
     *
     * @return Bvb_Grid_Source_Doctrine
     */
    protected function _setSelectParts()
    {
        $return = array();
        $selects = $this->_query->getDqlPart('select');
        if (empty($selects)) {
            $this->_findAndSetSelect();
            $selects = $this->_query->getDqlPart('select');
        }
        //Remove all 'as' instances
        $selects = $this->_removeAs($selects);
        foreach ($selects as $select) {
            $fields = explode(',', $select);
            $fields = array_map('trim', $fields);
            foreach ($fields as $field) {
                $fieldName = trim($field);
                $fieldAlias = null;
                if (strpos($field, ' ') !== false) {
                    // since our field expression may contain spaces, assume the last space marks the alias
                    $parts      = explode(' ', $field);
                    $fieldAlias = array_pop($parts);
                    $fieldName  = implode(' ', $parts);
                }
                if (empty($fieldAlias)) {
                    $pos = strpos($fieldName, '.');
                    $fieldAlias = substr($fieldName, ++$pos);
                }
                $return[$fieldAlias] = array(
                    'title' => ucwords(str_replace('_', ' ', $fieldAlias)),
                    'field' => $fieldName
                );
            }
        }
        $this->_queryParts['select'] = $return;
        return $this;
    }
    /**
     * Used to parse out the FROM and JOIN pieces of the DQL
     * and place it in the $_queryParts array for use
     * in many other places
     *
     * @return Bvb_Grid_Source_Doctrine
     */
    protected function _setFromParts()
    {
        $return = array();
        //Remove all 'as' instances
        $froms = $this->_removeAs($this->_query->getDqlPart('from'));
        foreach ($froms as $from) {
            $fields = explode(',', $from);
            $fields = array_map('trim', $fields);
            foreach ($fields as $field) {
                if (strpos(strtoupper($field), 'JOIN') === false) {
                    $this->_queryParts = array_merge_recursive($this->_queryParts, $this->_explodeFrom($field));
                } else {
                    $join = explode('JOIN', $field);
                    $join = array_map('trim', $join);
                    $joinType = strtolower($join[0]);
                    $this->_queryParts = array_merge_recursive($this->_queryParts, $this->_explodeJoin($join[1], $joinType));
                }
            }
        }
        return $this;
    }
    /**
     * Used to set SELECT values to the DQL when
     * no SELECT is provided.  We will just add
     * ALL columns for all tables given.
     *
     * NOTE: Since no SELECT was provided, to access these
     * from within the Bvb_Grid_Data class, you will need to
     * use the table alias + "_" + column name
     *
     * <code>
     * $grid->setGridColumns(array('co_code', 'co_name', 'co_continent', 'ci_name'));
     * </code>
     *
     * @return void
     */
    protected function _findAndSetSelect()
    {
        $return = array();
        //Make sure we have the FROM set
        // $this->_setFromParts(); // second call, if commented works
        $fromTableModel = $this->_queryParts['from']['tableModel'];
        $fromClass = Doctrine::getTable($fromTableModel);
        $fromColumns = array_keys($fromClass->getColumns());
        $fromAlias = $this->_queryParts['from']['alias'];
        foreach ($fromColumns as $fromColumn) {
            /**
             * Do this check here because a DQL with no JOIN,
             * does not need a table alias
             *
             * @var string
             */
            $addColumn = (!empty($fromAlias)) ? $fromAlias . '.' . $fromColumn : $fromColumn;
            $this->_query->addSelect($addColumn);
        }
        $joins = $this->_queryParts['join'];
        if (!empty($joins)) {
            foreach ($joins as $joinType) {
                foreach ($joinType as $join) {
                    $joinClass = Doctrine::getTable($join['tableModel']);
                    $joinColumns = array_keys($joinClass->getColumns());
                    foreach ($joinColumns as $joinColumn) {
                        $this->_query->addSelect($join['alias'] . '.' . $joinColumn);
                    }
                }
            }
        }
    }
    /**
     * Take a DQL SELECT string and parse it into
     * a usable array
     *
     * <code>
     * $return = array(
     *     'from' => array(
     *         'alias' => 'c',
     *         'table' => 'country'
     *     )
     * )
     * </code>
     *
     * @param string $from A DQL SELECT statement
     * @return array
     */
    private function _explodeFrom($from)
    {
        if (!is_string($from)) {
            require_once 'Bvb/Grid/Source/Doctrine/Exception.php';
            throw new Bvb_Grid_Source_Doctrine_Exception('Provided param needs to be a string only');
        }
        $return = array();
        $table = $from;
        $alias = null;
        if (count(explode(' ', $from)) > 1) {
            list($table, $alias) = explode(' ', $from);
        }
        if (strpos($table, '.') !== false) {
            $return = $this->_explodeJoin($from, 'left');
        } else {
            $return['from'] = array(
                'alias' => $alias,
                'tableModel' => $table,
                'tableName' => Doctrine::getTable($table)->getTableName()
            );
        }
        return $return;
    }
    /**
     * Take a DQL JOIN string and parse it into
     * a usable array
     *
     * <code>
     * $return = array(
     *     'join' => array(
     *         'left' => array(
     *             array(
     *                 'alias' => 'ci'
     *                 'table' => 'Model_City' //Doctrine Table name
     *                 'tableAlias' => 'City' //What is used in the JOIN statement
     *                 'joinOn' => 'c'
     *             )
     *         )
     *     )
     * )
     * </code>
     *
     * @param string $join
     * @param string $joinType The type of join - LEFT, RIGHT, etc
     */
    private function _explodeJoin($join, $joinType)
    {
        $return = array();
        list($table, $alias) = explode(' ', $join);
        list($joinOn, $tableAlias) = explode('.', $table);
        $mainTable = $this->_getModelFromAlias($joinOn);
        $tableModel = Doctrine::getTable($mainTable)->getRelation($tableAlias)->getClass();
        $return['join'][strtolower($joinType)][] = array(
            'alias'      => $alias,
            'tableModel' => $tableModel,
            'tableAlias' => $tableAlias,
            'joinOn'     => $joinOn
        );
        return $return;
    }
    /**
     * Simple utility for removing "as" from the DQL
     *
     * @param string $subject
     * @return string No-As DQL expression
     */
    private function _removeAs($subject)
    {
        return str_ireplace(' as', '', $subject);
    }
    /**
     * Find the table for which a column belongs
     *
     * @param string $column
     * @return string Name of the table used
     */
    private function _getModelFromColumn($column)
    {
        if (!is_string($column)) {
            $type = gettype($column);
            require_once 'Bvb/Grid/Source/Doctrine/Exception.php';
            throw new Bvb_Grid_Source_Doctrine_Exception('The $column param needs to be a string, ' . $type . ' provided');
        }
        if (empty($this->_queryParts['from']['alias'])) {
            return $this->_queryParts['from']['tableModel'];
        }
        list($alias, $field) = explode('.', $column);
        return $this->_getModelFromAlias($alias);
    }
    /**
     * Find the table/model based on the table alias provided
     *
     * @param string $alias
     * @return string
     */
    private function _getModelFromAlias($alias)
    {
        if (!is_string($alias)) {
            $type = gettype($alias);
            require_once 'Bvb/Grid/Source/Doctrine/Exception.php';
            throw new Bvb_Grid_Source_Doctrine_Exception('The $alias param needs to be a string, ' . $type . ' provided');
        }
        if ($this->_queryParts['from']['alias'] == $alias) {
            return $this->_queryParts['from']['tableModel'];
        }
        foreach ($this->_queryParts['join'] as $joins) {
            foreach ($joins as $join) {
                if ($join['alias'] == $alias) {
                    return $join['tableModel'];
                }
            }
        }
    }
    /**
     * Find the table/model based on the table alias provided
     *
     * @param string $table Name of table to find model from
     * @return string Name of the model associated with the provided table
     */
    private function _getModelFromTable($table)
    {
        if (!is_string($table)) {
            $type = gettype($table);
            require_once 'Bvb/Grid/Source/Doctrine/Exception.php';
            throw new Bvb_Grid_Source_Doctrine_Exception('The $table param needs to be a string, ' . $type . ' provided');
        }
        if ($this->_queryParts['from']['tableName'] == $table) {
            return $this->_queryParts['from']['tableModel'];
        }
        foreach ($this->_queryParts['join'] as $joins) {
            foreach ($joins as $join) {
                if ($join['tableAlias'] == $table) {
                    return $join['tableModel'];
                }
            }
        }
    }
    /**
     * @todo Implement
     * @see library/Bvb/Grid/Source/Bvb_Grid_Source_SourceInterface::getMassActionsIds()
     */
    public function getMassActionsIds ($table, $fields, $separator = '-')
    {
        $q= clone $this->_query;
        $alias = $this->_queryParts['from']['alias'];
        $q->removeDqlQueryPart('limit');
        $q->removeDqlQueryPart('offset');
        $q->removeDqlQueryPart('orderby');
        $q->removeSqlQueryPart('limit');
        $q->removeSqlQueryPart('offset');
        $q->removeSqlQueryPart('orderby');
        if ( count($fields) == 0 ) {
            $pks = Doctrine::getTable($this->_getModelFromTable($table))->getIdentifier();
        } else {
            $pks = $fields;
        }
        if ($pks && !is_array($pks))
        {
            $pks= array($pks);
        }
        if ( count($pks) > 1 ) {
            $concat = '';
            foreach ( $pks as $conc ) {
                $concat .= $alias.'.'.$conc . " ,'$separator' ,";
            }
            $concat = rtrim($concat, "'-' ,");
            $q->select('CONCAT('.$concat.', "_") as ids');
        } else {
            $concat = $pks[0];
            $q->select($alias.'.'.$concat.' as ids');
        }
        $result= $q->getConnection()->fetchAssoc($q->getSqlQuery(), $q->getFlattenedParams());
        $return = array();
        foreach ( $result as $value ) {
            $return[] = current($value);
        }
        return implode(',', $return);
    }
    /**
     * @todo Implement
     * @see library/Bvb/Grid/Source/Bvb_Grid_Source_SourceInterface::getValuesForFiltersFromTable()
     */
    public function getValuesForFiltersFromTable($table, $field, $fieldValue, $order = 'name ASC')
    {
        throw new Exception('Not yet Implemented');
    }
    /**
     * @todo Implement
     * @see library/Bvb/Grid/Source/Bvb_Grid_Source_SourceInterface::getAutoCompleteForFilter()
     */
    public function getAutoCompleteForFilter( $term, $field, $specialKey='', $output = 'json')
    {
        throw new Exception('Not yet Implemented');
    }
    /**
     * Defines total records
     *
     * @param int $total Total records found
     */
    public function setTotalRecords($total)
    {
        $this->_totalRecords = (int) $total;
    }
    public function beginTransaction()
    {
       return false;
    }
    public function commit()
    {
        return false;
    }
    public function rollBack()
    {
        return false;
    }
    public function getConnectionId()
    {
      return 0;
    }
}
 
  |