diff --git a/src/Bllim/Datatables/Datatables.php b/src/Bllim/Datatables/Datatables.php index e6601c50..33b86929 100644 --- a/src/Bllim/Datatables/Datatables.php +++ b/src/Bllim/Datatables/Datatables.php @@ -612,9 +612,12 @@ protected function includeInArray($item, $array) */ protected function paging() { - if (!is_null($this->input['start']) && !is_null($this->input['length']) && $this->input['length'] != -1) { - $this->query->skip($this->input['start'])->take((int)$this->input['length'] > 0 ? $this->input['length'] : 10); - } + if(!is_null($this->input['start']) && !is_null($this->input['length'])) + { + if($this->input['length'] > 0){ + $this->query->skip($this->input['start'])->take((int)$this->input['length']); + } + } } /** diff --git a/src/Bllim/Datatables/Datatables.php~ b/src/Bllim/Datatables/Datatables.php~ new file mode 100644 index 00000000..e6601c50 --- /dev/null +++ b/src/Bllim/Datatables/Datatables.php~ @@ -0,0 +1,1070 @@ + + */ + +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Input; +use Illuminate\Support\Facades\Config; +use Illuminate\Support\Facades\Response; +use Illuminate\Support\Arr; +use Illuminate\Support\Str; +use Illuminate\View\Compilers\BladeCompiler; +use Illuminate\Filesystem\Filesystem; + +class Datatables +{ + /** + * @var \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query + */ + public $query; + + /** + * @var string $query_type 'eloquent' | 'fluent' + */ + protected $query_type; + + protected $added_columns = array(); + protected $removed_columns = array(); + protected $edit_columns = array(); + protected $filter_columns = array(); + protected $sColumns = array(); + + public $columns = array(); + public $aliased_ordered_columns = array(); + + protected $count_all = 0; + protected $display_all = 0; + + protected $result_object; + protected $result_array = array(); + protected $result_array_return = array(); + + protected $input = array(); + protected $mDataSupport; //previous support included only returning columns as object with key names + protected $dataFullSupport; //new support that better implements dot notation without reliance on name column + + protected $index_column; + protected $row_class_tmpl = null; + protected $row_data_tmpls = array(); + + + /** + * Read Input into $this->input according to jquery.dataTables.js version + * + */ + public function __construct() + { + + $this->setData($this->processData(Input::get())); + + return $this; + } + + /** + * Will take an input array and return the formatted dataTables data as an array + * + * @param array $input + * + * @return array + */ + public function processData($input = []) + { + $formatted_input = []; + + if (isset($input['draw'])) { + // DT version 1.10+ + + $input['version'] = '1.10'; + + $formatted_input = $input; + + } else { + // DT version < 1.10 + + $formatted_input['version'] = '1.9'; + + $formatted_input['draw'] = Arr::get($input, 'sEcho', ''); + $formatted_input['start'] = Arr::get($input, 'iDisplayStart', 0); + $formatted_input['length'] = Arr::get($input, 'iDisplayLength', 10); + $formatted_input['search'] = array( + 'value' => Arr::get($input, 'sSearch', ''), + 'regex' => Arr::get($input, 'bRegex', ''), + ); + $formatted_input['_'] = Arr::get($input, '_', ''); + + $columns = explode(',', Arr::get($input, 'sColumns', '')); + $formatted_input['columns'] = array(); + for ($i = 0; $i < Arr::get($input, 'iColumns', 0); $i++) { + $arr = array(); + $arr['name'] = isset($columns[$i]) ? $columns[$i] : ''; + $arr['data'] = Arr::get($input, 'mDataProp_' . $i, ''); + $arr['searchable'] = Arr::get($input, 'bSearchable_' . $i, ''); + $arr['search'] = array(); + $arr['search']['value'] = Arr::get($input, 'sSearch_' . $i, ''); + $arr['search']['regex'] = Arr::get($input, 'bRegex_' . $i, ''); + $arr['orderable'] = Arr::get($input, 'bSortable_' . $i, ''); + $formatted_input['columns'][] = $arr; + } + + $formatted_input['order'] = array(); + for ($i = 0; $i < Arr::get($input, 'iSortingCols', 0); $i++) { + $arr = array(); + $arr['column'] = Arr::get($input, 'iSortCol_' . $i, ''); + $arr['dir'] = Arr::get($input, 'sSortDir_' . $i, ''); + $formatted_input['order'][] = $arr; + } + } + + return $formatted_input; + } + + /** + * @return array $this->input + */ + public function getData() + { + return $this->input; + } + + /** + * Sets input data. + * Can be used when not wanting to use default Input data. + * + * @param array $data + */ + public function setData($data) + { + $this->input = $data; + } + + /** + * Gets query and returns instance of class + * + * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query + * @param null $dataFullSupport + * + * @return Datatables + */ + public static function of($query, $dataFullSupport = null) + { + $ins = new static; + $ins->dataFullSupport = ($dataFullSupport) ?: Config::get('datatables::dataFullSupport', false); + $ins->saveQuery($query); + + return $ins; + } + + /** + * Organizes works + * + * @param bool $mDataSupport + * @param bool $raw + * + * @return array|json + */ + public function make($mDataSupport = false, $raw = false) + { + $this->mDataSupport = $mDataSupport; + $this->createAliasedOrderedColumns(); + $this->prepareQuery(); + $this->getResult(); + $this->modifyColumns(); + $this->regulateArray(); + + return $this->output($raw); + } + + /** + * Gets results from prepared query + * + * @return null + */ + protected function getResult() + { + if ($this->query_type == 'eloquent') { + $this->result_object = $this->query->get(); + $this->result_array = $this->result_object->toArray(); + } else { + $this->result_object = $this->query->get(); + $this->result_array = array_map(function ($object) { + return (array)$object; + }, $this->result_object); + } + + if ($this->dataFullSupport) { + $walk = function ($value, $key, $prefix = null) use (&$walk, &$result_array) { + $key = (!is_null($prefix)) ? ($prefix . "." . $key) : $key; + if (is_array($value)) { + array_walk($value, $walk, $key); + } else { + $result_array = Arr::add($result_array, $key, $value); + } + }; + + $result_array = array(); + array_walk($this->result_array, $walk); + $this->result_array = $result_array; + + } + + } + + /** + * Prepares variables according to Datatables parameters + * + * @return null + */ + protected function prepareQuery() + { + $this->count('count_all'); //Total records + $this->filtering(); + $this->count('display_all'); // Filtered records + $this->paging(); + $this->ordering(); + } + + /** + * Adds additional columns to added_columns + * + * @param string $name + * @param string|callable $content + * @param bool $order + * + * @return $this + */ + public function addColumn($name, $content, $order = false) + { + $this->sColumns[] = $name; + + $this->added_columns[] = array('name' => $name, 'content' => $content, 'order' => $order); + + return $this; + } + + /** + * Adds column names to edit_columns + * + * @param string $name + * @param string|callable $content + * + * @return $this + */ + public function editColumn($name, $content) + { + $this->edit_columns[] = array('name' => $name, 'content' => $content); + + return $this; + } + + + /** + * This will remove the columns from the returned data. It will also cause it to skip any filters for those removed columns. + * Adds a list of columns to removed_columns + * + * @params strings ...,... As many individual string parameters matching column names + * + * @return $this + */ + public function removeColumn() + { + $names = func_get_args(); + $this->removed_columns = array_merge($this->removed_columns, $names); + + return $this; + } + + /** + * The filtered columns will add query sql options for the specified columns + * Adds column filter to filter_columns + * + * @param string $column + * @param string $method + * @param mixed ...,... All the individual parameters required for specified $method + * + * @return $this + */ + public function filterColumn($column, $method) + { + $params = func_get_args(); + $this->filter_columns[$column] = array('method' => $method, 'parameters' => array_splice($params, 2)); + + return $this; + } + + + /** + * Sets the DT_RowID for the DataTables index column (as used to set, e.g., id of the tags) to the named column + * If the index matches a column, then that column value will be set as the id of th . + * If the index doesn't, it will be parsed as either a callback or blade template and that returned value will be the + * id of the + * + * @param string $name + * + * @return $this + */ + public function setIndexColumn($name) + { + $this->index_column = $name; + + return $this; + } + + /** + * Sets DT_RowClass template + * result: + * + * @param string|callable $content + * + * @return $this + */ + public function setRowClass($content) + { + $this->row_class_tmpl = $content; + + return $this; + } + + /** + * Sets DT_RowData template for given attribute name + * result: Datatables invoking $(row).data(name, output_from_your_template) + * + * @param string $name + * @param string|callable $content + * + * @return $this + */ + public function setRowData($name, $content) + { + $this->row_data_tmpls[$name] = $content; + + return $this; + } + + /** + * Saves given query and determines its type + * + * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query + * + * @return null + */ + protected function saveQuery($query) + { + $this->query = $query; + $this->query_type = $query instanceof \Illuminate\Database\Query\Builder ? 'fluent' : 'eloquent'; + if ($this->dataFullSupport) { + $this->columns = array_map(function ($column) { + return trim(DB::connection()->getPdo()->quote($column['data']), "'"); + }, $this->input['columns']); + } else { + $this->columns = $this->query_type == 'eloquent' ? ($this->query->getQuery()->columns ?: array()) : ($this->query->columns ?: array()); + } + } + + /** + * Places extra columns + * + * @return null + */ + protected function modifyColumns() + { + foreach ($this->result_array as $rkey => &$rvalue) { + foreach ($this->added_columns as $key => $value) { + $value['content'] = $this->getContent($value['content'], $rvalue, $this->result_object[$rkey]); + + if ($this->dataFullSupport) { + Arr::set($rvalue, $value['name'], $value['content']); + } else { + $rvalue = $this->includeInArray($value, $rvalue); + } + } + + foreach ($this->edit_columns as $key => $value) { + $value['content'] = $this->getContent($value['content'], $rvalue, $this->result_object[$rkey]); + + if ($this->dataFullSupport) { + Arr::set($rvalue, $value['name'], $value['content']); + } else { + $rvalue[$value['name']] = $value['content']; + } + } + } + } + + /** + * Converts result_array number indexed array and consider excess columns + * + * @return null + * @throws \Exception + */ + protected function regulateArray() + { + foreach ($this->result_array as $key => $value) { + foreach ($this->removed_columns as $remove_col_name) { + if ($this->dataFullSupport) { + Arr::forget($value, $remove_col_name); + } else { + unset($value[$remove_col_name]); + } + } + + if ($this->mDataSupport || $this->dataFullSupport) { + $row = $value; + } else { + $row = array_values($value); + } + + if ($this->index_column !== null) { + if (array_key_exists($this->index_column, $value)) { + $row['DT_RowId'] = $value[$this->index_column]; + } else { + $row['DT_RowId'] = $this->getContent($this->index_column, $value, $this->result_object[$key]); + } + } + + if ($this->row_class_tmpl !== null) { + $row['DT_RowClass'] = $this->getContent($this->row_class_tmpl, $value, $this->result_object[$key]); + } + + if (count($this->row_data_tmpls)) { + $row['DT_RowData'] = array(); + foreach ($this->row_data_tmpls as $tkey => $tvalue) { + $row['DT_RowData'][$tkey] = $this->getContent($tvalue, $value, $this->result_object[$key]); + } + } + + $this->result_array_return[] = $row; + } + } + + /** + * + * Inject searched string into $1 in filter_column parameters + * + * @param array|callable|string|Expression &$params + * @param string $value + * + * @return array + */ + private function injectVariable(&$params, $value) + { + if (is_array($params)) { + foreach ($params as $key => $param) { + $params[$key] = $this->injectVariable($param, $value); + } + + } elseif ($params instanceof \Illuminate\Database\Query\Expression) { + $params = DB::raw(str_replace('$1', $value, $params)); + + } elseif (is_callable($params)) { + $params = $params($value); + + } elseif (is_string($params)) { + $params = str_replace('$1', $value, $params); + } + + return $params; + } + + /** + * Creates an array which contains published aliased ordered columns in sql with their index + * + * Creates an array of column names using column aliases where applicable. + * If an added column has a particular order number, it will skip that array key # + * and continue to the next. Leaves dot notation in column names alone. + * + * @return null + */ + protected function createAliasedOrderedColumns() + { + $added_columns_indexes = array(); + $aliased_ordered_columns = array(); + $count = 0; + + foreach ($this->added_columns as $key => $value) { + if ($value['order'] === false) { + continue; + } + $added_columns_indexes[] = $value['order']; + } + + for ($i = 0, $c = count($this->columns); $i < $c; $i++) { + + if (in_array($this->getColumnName($this->columns[$i]), $this->removed_columns)) { + continue; + } + + if (in_array($count, $added_columns_indexes)) { + $count++; + $i--; + continue; + } + + // previous regex #^(\S*?)\s+as\s+(\S*?)$# prevented subqueries and functions from being detected as alias + preg_match('#\s+as\s+(\S*?)$#si', $this->columns[$i], $matches); + $aliased_ordered_columns[$count] = empty($matches) ? $this->columns[$i] : $matches[1]; + $count++; + } + + $this->aliased_ordered_columns = $aliased_ordered_columns; + } + + /** + * Determines if content is callable or blade string, processes and returns + * + * @param string|callable $content Pre-processed content + * @param mixed $data data to use with blade template + * @param mixed $param parameter to call with callable + * + * @return string Processed content + */ + protected function getContent($content, $data = null, $param = null) + { + if (is_string($content)) { + $return = $this->blader($content, $data); + } elseif (is_callable($content)) { + $return = $content($param); + } else { + $return = $content; + } + + return $return; + } + + /** + * Parses and compiles strings by using Blade Template System + * + * @param string $str + * @param array $data + * + * @return string + * @throws \Exception + */ + protected function blader($str, $data = array()) + { + $empty_filesystem_instance = new Filesystem; + $blade = new BladeCompiler($empty_filesystem_instance, 'datatables'); + $parsed_string = $blade->compileString($str); + + ob_start() and extract($data, EXTR_SKIP); + + try { + eval('?>' . $parsed_string); + } + catch (\Exception $e) { + ob_end_clean(); + throw $e; + } + + $str = ob_get_contents(); + ob_end_clean(); + + return $str; + } + + /** + * Places item of extra columns into result_array by care of their order + * Only necessary if not using mData + * + * @param array $item + * @param array $array + * + * @return null + */ + protected function includeInArray($item, $array) + { + if ($item['order'] === false) { + return array_merge($array, array($item['name'] => $item['content'])); + } else { + $count = 0; + $last = $array; + $first = array(); + + if (count($array) <= $item['order']) { + return $array + array($item['name'] => $item['content']); + } + + foreach ($array as $key => $value) { + if ($count == $item['order']) { + return array_merge($first, array($item['name'] => $item['content']), $last); + } + + unset($last[$key]); + $first[$key] = $value; + + $count++; + } + } + } + + /** + * Datatable paging + * + * @return null + */ + protected function paging() + { + if (!is_null($this->input['start']) && !is_null($this->input['length']) && $this->input['length'] != -1) { + $this->query->skip($this->input['start'])->take((int)$this->input['length'] > 0 ? $this->input['length'] : 10); + } + } + + /** + * Datatable ordering + * + * @return null + */ + protected function ordering() + { + if (array_key_exists('order', $this->input) && count($this->input['order']) > 0) { + $columns = $this->cleanColumns($this->aliased_ordered_columns); + + for ($i = 0, $c = count($this->input['order']); $i < $c; $i++) { + $order_col = (int)$this->input['order'][$i]['column']; + if (isset($columns[$order_col])) { + if ($this->input['columns'][$order_col]['orderable'] == "true") { + $this->query->orderBy($columns[$order_col], $this->input['order'][$i]['dir']); + } + } + } + + } + } + + /** + * @param array $cols + * @param bool $use_alias weather to get the column/function or the alias + * + * @return array + */ + protected function cleanColumns($cols, $use_alias = true) + { + $return = array(); + foreach ($cols as $i => $col) { + preg_match('#^(.*?)\s+as\s+(\S*?)\s*$#si', $col, $matches); + if (empty($matches)) { + $return[$i] = $use_alias ? $this->getColumnName($col) : $col; + } else { + $return[$i] = $matches[$use_alias ? 2 : 1]; + } + + } + + return $return; + } + + /** + * Datatable filtering + * + * @return null + */ + protected function filtering() + { + + // copy of $this->columns without columns removed by remove_column + $columns_not_removed = $this->columns; + for ($i = 0, $c = count($columns_not_removed); $i < $c; $i++) { + if (in_array($this->getColumnName($columns_not_removed[$i]), $this->removed_columns)) { + unset($columns_not_removed[$i]); + } + } + + //reindex keys if columns were removed + $columns_not_removed = array_values($columns_not_removed); + + // copy of $this->columns cleaned for database queries + $column_names = $this->cleanColumns($columns_not_removed, false); + $column_aliases = $this->cleanColumns($columns_not_removed, !$this->dataFullSupport); + + // global search + if ($this->input['search']['value'] != '') { + $that = $this; + + $this->query->where(function ($query) use (&$that, $column_aliases, $column_names) { + + for ($i = 0, $c = count($that->input['columns']); $i < $c; $i++) { + if (isset($column_aliases[$i]) && $that->input['columns'][$i]['searchable'] == "true") { + + // if filter column exists for this columns then use user defined method + if (isset($that->filter_columns[$column_aliases[$i]])) { + + $filter = $that->filter_columns[$column_aliases[$i]]; + + // check if "or" equivalent exists for given function + // and if the number of parameters given is not excess + // than call the "or" equivalent + + $method_name = 'or' . ucfirst($filter['method']); + + if (method_exists($query->getQuery(), $method_name) + && count($filter['parameters']) <= with(new \ReflectionMethod($query->getQuery(), $method_name))->getNumberOfParameters() + ) { + + if (isset($filter['parameters'][1]) + && strtoupper(trim($filter['parameters'][1])) == "LIKE" + ) { + $keyword = $that->formatKeyword($that->input['search']['value']); + } else { + $keyword = $that->input['search']['value']; + } + + call_user_func_array( + array( + $query, + $method_name + ), + $that->injectVariable( + $filter['parameters'], + $keyword + ) + ); + } + + } else { + // otherwise do simple LIKE search + + $keyword = $that->formatKeyword($that->input['search']['value']); + + // Check if the database driver is PostgreSQL + // If it is, cast the current column to TEXT datatype + $cast_begin = null; + $cast_end = null; + if ($this->databaseDriver() === 'pgsql') { + $cast_begin = "CAST("; + $cast_end = " as TEXT)"; + } + + //there's no need to put the prefix unless the column name is prefixed with the table name. + $column = $this->prefixColumn($column_names[$i]); + + if (Config::get('datatables::search.case_insensitive', false)) { + $query->orwhere(DB::raw('LOWER(' . $cast_begin . $column . $cast_end . ')'), 'LIKE', Str::lower($keyword)); + } else { + $query->orwhere(DB::raw($cast_begin . $column . $cast_end), 'LIKE', $keyword); + } + } + + } + } + }); + + } + + // column search + for ($i = 0, $c = count($this->input['columns']); $i < $c; $i++) { + if (isset($column_aliases[$i]) && $this->input['columns'][$i]['orderable'] == "true" && $this->input['columns'][$i]['search']['value'] != '') { + // if filter column exists for this columns then use user defined method + if (isset($this->filter_columns[$column_aliases[$i]])) { + + $filter = $this->filter_columns[$column_aliases[$i]]; + + if (isset($filter['parameters'][1]) + && strtoupper(trim($filter['parameters'][1])) == "LIKE" + ) { + $keyword = $this->formatKeyword($this->input['columns'][$i]['search']['value']); + } else { + $keyword = $this->input['columns'][$i]['search']['value']; + } + + + call_user_func_array( + array( + $this->query, + $filter['method'] + ), + $this->injectVariable( + $filter['parameters'], + $keyword + ) + ); + + } else // otherwise do simple LIKE search + { + + $keyword = $this->formatKeyword($this->input['columns'][$i]['search']['value']); + + //there's no need to put the prefix unless the column name is prefixed with the table name. + $column = $this->prefixColumn($column_names[$i]); + + if (Config::get('datatables::search.case_insensitive', false)) { + $this->query->where(DB::raw('LOWER(' . $column . ')'), 'LIKE', Str::lower($keyword)); + } else { + //note: so, when would a ( be in the columns? It will break a select if that's put in the columns + //without a DB::raw. It could get there in filter columns, but it wouldn't be delt with here. + //why is it searching for ( ? + $col = strstr($column_names[$i], '(') ? DB::raw($column) : $column; + $this->query->where($col, 'LIKE', $keyword); + } + } + } + } + } + + /** + * This will format the keyword as needed for "LIKE" based on config settings + * If $value already has %, it doesn't motify and just returns the value. + * + * @param string $value + * + * @return string + */ + public function formatKeyword($value) + { + if (strpos($value, '%') !== false) { + return $value; + } + + if (Config::get('datatables::search.use_wildcards', false)) { + $keyword = '%' . $this->formatWildcard($value) . '%'; + } else { + $keyword = '%' . trim($value) . '%'; + } + + return $keyword; + } + + /** + * Adds % wildcards to the given string + * + * @param $str + * @param bool $lowercase + * + * @return string + */ + public function formatWildcard($str, $lowercase = true) + { + if ($lowercase) { + $str = lowercase($str); + } + + return preg_replace('\s+', '%', $str); + } + + /** + * Returns current database prefix + * + * @return string + */ + public function databasePrefix() + { + if ($this->query_type == 'eloquent') { + $query = $this->query->getQuery(); + } else { + $query = $this->query; + } + return $query->getGrammar()->getTablePrefix(); + //return Config::get('database.connections.' . Config::get('database.default') . '.prefix', ''); + } + + /** + * Returns current database driver + */ + protected function databaseDriver() + { + if ($this->query_type == 'eloquent') { + $query = $this->query->getQuery(); + } else { + $query = $this->query; + } + return $query->getConnection()->getDriverName(); + } + + /** + * Will prefix column if needed + * + * @param string $column + * @return string + */ + protected function prefixColumn($column) + { +// $query = ($this->query_type == 'eloquent') ? $this->query->getQuery() : $this->query; +// return $query->getGrammar()->wrap($column); + + $table_names = $this->tableNames(); + if (count(array_filter($table_names, function($value) use (&$column) { return strpos($column, $value.".") === 0; }))) { + //the column starts with one of the table names + $column = $this->databasePrefix() . $column; + } + return $column; + } + + /** + * Will look through the query and all it's joins to determine the table names + * + * @return array + */ + protected function tableNames() + { + $names = []; + + $query = ($this->query_type == 'eloquent') ? $this->query->getQuery() : $this->query; + + $names[] = $query->from; + $joins = $query->joins?:array(); + $databasePrefix = $this->databasePrefix(); + foreach ($joins as $join) { + $table = preg_split("/ as /i", $join->table); + $names[] = $table[0]; + if (isset($table[1]) && !empty($databasePrefix) && strpos($table[1], $databasePrefix) == 0) { + $names[] = preg_replace('/^'.$databasePrefix.'/', '', $table[1]); + } + } + + return $names; + + } + + /** + * Counts current query + * + * @param string $count variable to store to 'count_all' for iTotalRecords, 'display_all' for iTotalDisplayRecords + * + * @return null + */ + protected function count($count = 'count_all') + { + + //Get columns to temp var. + if ($this->query_type == 'eloquent') { + $query = $this->query->getQuery(); + $connection = $this->query->getModel()->getConnection()->getName(); + } else { + $query = $this->query; + $connection = $query->getConnection()->getName(); + } + + // if its a normal query ( no union ) replace the select with static text to improve performance + $countQuery = clone $query; + if (!preg_match('/UNION/i', $countQuery->toSql())) { + $countQuery->select(DB::raw("'1' as row")); + + // if query has "having" clause add select columns + if ($countQuery->havings) { + foreach ($countQuery->havings as $having) { + if (isset($having['column'])) { + $countQuery->addSelect($having['column']); + } else { + // search filter_columns for query string to get column name from an array key + $found = false; + foreach ($this->filter_columns as $column => $filter) { + if ($filter['parameters'][0] == $having['sql']) { + $found = $column; + break; + } + } + // then correct it if it's an alias and add to columns + if ($found !== false) { + foreach ($this->columns as $col) { + $arr = preg_split('/ as /i', $col); + if (isset($arr[1]) && $arr[1] == $found) { + $found = $arr[0]; + break; + } + } + $countQuery->addSelect($found); + } + } + } + } + } + + // Clear the orders, since they are not relevant for count + $countQuery->orders = null; + + $this->$count = DB::connection($connection) + ->table(DB::raw('(' . $countQuery->toSql() . ') AS count_row_table')) + ->setBindings($countQuery->getBindings())->count(); + + } + + /** + * Returns column name from . + * + * For processing select statement columns like $query->column data + * + * @param string $str + * + * @return string + */ + protected function getColumnName($str) + { + + preg_match('#^(\S*?)\s+as\s+(\S*?)$#si', $str, $matches); + + if (!empty($matches)) { + return $matches[2]; + } elseif (strpos($str, '.')) { + $array = explode('.', $str); + + return array_pop($array); + } + + return $str; + } + + /** + * Prints output + * + * @param bool $raw If raw will output array data, otherwise json + * + * @return array|json + */ + protected function output($raw = false) + { + if (Arr::get($this->input, 'version') == '1.10') { + + $output = array( + "draw" => intval($this->input['draw']), + "recordsTotal" => $this->count_all, + "recordsFiltered" => $this->display_all, + "data" => $this->result_array_return, + ); + + } else { + + $sColumns = array_merge_recursive($this->columns, $this->sColumns); + + $output = array( + "sEcho" => intval($this->input['draw']), + "iTotalRecords" => $this->count_all, + "iTotalDisplayRecords" => $this->display_all, + "aaData" => $this->result_array_return, + "sColumns" => $sColumns + ); + + } + + if (Config::get('app.debug', false)) { + $output['aQueries'] = DB::getQueryLog(); + } + + if ($raw) { + return $output; + } else { + return Response::json($output); + } + } + + /** + * originally PR #93 + * Allows previous API calls where the methods were snake_case. + * Will convert a camelCase API call to a snake_case call. + */ + public function __call($name, $arguments) + { + $name = Str::camel(Str::lower($name)); + if (method_exists($this, $name)) { + return call_user_func_array(array($this, $name), $arguments); + } else { + trigger_error('Call to undefined method ' . __CLASS__ . '::' . $name . '()', E_USER_ERROR); + } + } +}