seeder-migration-issues

This commit is contained in:
RafficMohammed
2023-01-30 14:23:34 +05:30
parent 4d918c722f
commit 2ec836b447
3628 changed files with 116006 additions and 187 deletions

View File

@@ -0,0 +1,37 @@
<?php namespace Chumper\Datatable\Columns;
/**
* Class BaseColumn
* @package Chumper\Datatable\Columns
*/
abstract class BaseColumn {
/**
* @var String name of the column
*/
protected $name;
/**
* @param $name
*/
function __construct($name)
{
$this->name = $name;
}
/**
* @param mixed $model The data to pass to the column,
* could be a model or an array
* @return mixed the return value of the implementation,
* should be text in most of the cases
*/
public abstract function run($model);
/**
* @return String The name of the column
*/
public function getName()
{
return $this->name;
}
}

View File

@@ -0,0 +1,74 @@
<?php namespace Chumper\Datatable\Columns;
class DateColumn extends BaseColumn {
/**
* Constants for the time representation
*/
const DATE = 0;
const TIME = 1;
const DATE_TIME = 2;
const CUSTOM = 4;
const FORMATTED_DATE = 5;
const DAY_DATE = 6;
/**
* @var int The format to show
*/
private $format;
/**
* @var string custom show string if chosen
*/
private $custom;
function __construct($name, $format = 2, $custom = "")
{
parent::__construct($name);
$this->format = $format;
$this->custom = $custom;
}
/**
* @param mixed $model The data to pass to the column,
* could be a model or an array
* @return mixed the return value of the implementation,
* should be text in most of the cases
*/
public function run($model)
{
if (is_string(is_array($model) ? $model[$this->name] : $model->{$this->name}))
{
if ($this->custom)
{
return strftime($this->custom, strtotime($model->{$this->name}));
}
return is_array($model) ? $model[$this->name] : $model->{$this->name};
}
switch($this->format)
{
case DateColumn::DATE:
return is_array($model) ? $model[$this->name]->toDateString(): $model->{$this->name}->toDateString();
break;
case DateColumn::TIME:
return is_array($model) ? $model[$this->name]->toTimeString(): $model->{$this->name}->toTimeString();
break;
case DateColumn::DATE_TIME:
return is_array($model) ? $model[$this->name]->toDateTimeString(): $model->{$this->name}->toDateTimeString();
break;
case DateColumn::CUSTOM:
return is_array($model) ? $model[$this->name]->format($this->custom): $model->{$this->name}->format($this->custom);
break;
case DateColumn::FORMATTED_DATE:
return is_array($model) ? $model[$this->name]->toFormattedDateString(): $model->{$this->name}->toFormattedDateString();
break;
case DateColumn::DAY_DATE:
return is_array($model) ? $model[$this->name]->toDayDateTimeString(): $model->{$this->name}->toDayDateTimeString();
break;
}
}
}

View File

@@ -0,0 +1,17 @@
<?php namespace Chumper\Datatable\Columns;
class FunctionColumn extends BaseColumn {
private $callable;
function __construct($name, $callable)
{
parent::__construct($name);
$this->callable = $callable;
}
public function run($model)
{
return call_user_func($this->callable,$model);
}
}

View File

@@ -0,0 +1,17 @@
<?php namespace Chumper\Datatable\Columns;
class TextColumn extends BaseColumn {
private $text;
function __construct($name, $text)
{
parent::__construct($name);
$this->text = $text;
}
public function run($model)
{
return $this->text;
}
}

View File

@@ -0,0 +1,52 @@
<?php namespace Chumper\Datatable;
use Chumper\Datatable\Engines\CollectionEngine;
use Chumper\Datatable\Engines\QueryEngine;
use Input;
/**
* Class Datatable
* @package Chumper\Datatable
*/
class Datatable {
/**
* @param $query
* @return QueryEngine
*/
public function query($query)
{
return new QueryEngine($query);
}
/**
* @param $collection
* @return CollectionEngine
*/
public function collection($collection)
{
return new CollectionEngine($collection);
}
/**
* @return Table
*/
public function table()
{
return new Table;
}
/**
* @return bool True if the plugin should handle this request, false otherwise
*/
public function shouldHandle()
{
$echo = Input::get('sEcho',null);
if(/*Request::ajax() && */!is_null($echo) && is_numeric($echo))
{
return true;
}
return false;
}
}

View File

@@ -0,0 +1,38 @@
<?php namespace Chumper\Datatable;
use Illuminate\Support\ServiceProvider;
class DatatableServiceProvider extends ServiceProvider {
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
public function boot()
{
// $this->package('chumper/datatable');
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('datatable', Datatable::class);
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return array('datatable');
}
}

View File

@@ -0,0 +1,601 @@
<?php namespace Chumper\Datatable\Engines;
use Exception;
use Chumper\Datatable\Columns\DateColumn;
use Chumper\Datatable\Columns\FunctionColumn;
use Chumper\Datatable\Columns\TextColumn;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Config;
/**
* Class BaseEngine
* @package Chumper\Datatable\Engines
*/
abstract class BaseEngine {
const ORDER_ASC = 'asc';
const ORDER_DESC = 'desc';
/**
* @var array
*/
protected $config = array();
/**
* @var mixed
*/
protected $rowClass = null;
/**
* @var mixed
*/
protected $rowId = null;
/**
* @var array
*/
protected $rowData = null;
/**
* @var array
*/
protected $columnSearches = array();
/**
* @var array
* support for DB::raw fields on where
*/
protected $fieldSearches = array();
/**
* @var array
* support for DB::raw fields on where
* sburkett - added for column-based exact matching
*/
protected $columnSearchExact = array();
/**
* @var
*/
protected $sEcho;
/**
* @var \Illuminate\Support\Collection
*/
protected $columns;
/**
* @var array
*/
protected $searchColumns = array();
/**
* @var array
*/
protected $showColumns = array();
/**
* @var array
*/
protected $orderColumns = array();
/**
* @var int
*/
protected $skip = 0;
/**
* @var null
*/
protected $limit = null;
/**
* @var null
*/
protected $search = null;
/**
* @var null
* Will be an array if order is set
* array(
* 0 => column
* 1 => name:cast:length
* )
*/
protected $orderColumn = null;
/**
* @var string
*/
protected $orderDirection = BaseEngine::ORDER_ASC;
/**
* @var boolean If the return should be alias mapped
*/
protected $aliasMapping = false;
/**
* @var bool If the search should be done with exact matching
*/
protected $exactWordSearch = false;
/**
* @var bool If you need to display all records.
*/
protected $enableDisplayAll = false;
/**
* @var mixed Additional data which passed from server to client.
*/
protected $additionalData = null;
function __construct()
{
$this->columns = new Collection();
$this->config = Config::get('datatable::engine');
$this->setExactWordSearch( isset($this->config['exactWordSearch'])? $this->config['exactWordSearch'] : false );
$this->setEnableDisplayAll( isset($this->config['enableDisplayAll'])? $this->config['enableDisplayAll'] : false );
return $this;
}
/**
* @return $this
* @throws \Exception
*/
public function addColumn()
{
if(func_num_args() != 2 && func_num_args() != 1)
throw new Exception('Invalid number of arguments');
if(func_num_args() == 1)
{
//add a predefined column
$this->columns->put(func_get_arg(0)->getName(), func_get_arg(0));
}
else if(is_callable(func_get_arg(1)))
{
$this->columns->put(func_get_arg(0), new FunctionColumn(func_get_arg(0), func_get_arg(1)));
}
else
{
$this->columns->put(func_get_arg(0), new TextColumn(func_get_arg(0),func_get_arg(1)));
}
return $this;
}
/**
* @param $name
* @return mixed
*/
public function getColumn($name)
{
return $this->columns->get($name,null);
}
/**
* @return array
*/
public function getOrder()
{
return array_keys($this->columns->toArray());
}
/**
* @return array
*/
public function getOrderingColumns()
{
return $this->orderColumns;
}
/**
* @return array
*/
public function getSearchingColumns()
{
return $this->searchColumns;
}
/**
* @return $this
*/
public function clearColumns()
{
$this->columns = new Collection();
return $this;
}
/**
* @param $cols
* @return $this
*/
public function showColumns($cols)
{
if ( ! is_array($cols)) {
$cols = func_get_args();
}
foreach ($cols as $property) {
//quick fix for created_at and updated_at columns
if(in_array($property, array('created_at', 'updated_at')))
{
$this->columns->put($property, new DateColumn($property, DateColumn::DAY_DATE));
}
else
{
$this->columns->put($property, new FunctionColumn($property, function($model) use($property){
try{return is_array($model)?$model[$property]:$model->$property;}catch(Exception $e){return null;}
}));
}
$this->showColumns[] = $property;
}
return $this;
}
/**
* Used to handle all the inputs directly from an engine, instead of from Datatables.
* @see QueryEngine
*/
protected function prepareEngine()
{
$this->handleInputs();
$this->prepareSearchColumns();
}
/**
* @return \Illuminate\Http\JsonResponse
*/
public function make()
{
$this->prepareEngine();
$output = array(
"aaData" => $this->internalMake($this->columns, $this->searchColumns)->toArray(),
"sEcho" => intval($this->sEcho),
"iTotalRecords" => $this->totalCount(),
"iTotalDisplayRecords" => $this->count(),
"aaAdditional" => $this->additionalData,
);
return Response::json($output);
}
/**
* @param $cols
* @return $this
*/
public function searchColumns($cols)
{
if ( ! is_array($cols)) {
$cols = func_get_args();
}
$this->searchColumns = $cols;
return $this;
}
/**
* @param $cols
* @return $this
*/
public function orderColumns($cols)
{
if ( ! is_array($cols)) {
$cols = func_get_args();
}
$this->orderColumns = $cols;
return $this;
}
/**
* @param $function Set a function for a dynamic row class
* @return $this
*/
public function setRowClass($function)
{
$this->rowClass = $function;
return $this;
}
/**
* @param $function Set a function for a dynamic row id
* @return $this
*/
public function setRowId($function)
{
$this->rowId = $function;
return $this;
}
/**
* @param $function Set a function for dynamic html5 data attributes
* @return $this
*/
public function setRowData($function)
{
$this->rowData = $function;
return $this;
}
public function setAliasMapping($value = true)
{
$this->aliasMapping = $value;
return $this;
}
public function setExactWordSearch($value = true)
{
$this->exactWordSearch = $value;
return $this;
}
public function setEnableDisplayAll($value = true)
{
$this->enableDisplayAll = $value;
return $this;
}
/**
* @param $columnNames Sets up a lookup table for which columns should use exact matching -sburkett
* @return $this
*/
public function setExactMatchColumns($columnNames)
{
foreach($columnNames as $columnIndex)
$this->columnSearchExact[ $columnIndex ] = true;
return $this;
}
public function setAdditionalData($data)
{
$this->additionalData = $data;
return $this;
}
public function getRowClass()
{
return $this->rowClass;
}
public function getRowId()
{
return $this->rowId;
}
public function getRowData()
{
return $this->rowData;
}
public function getAliasMapping()
{
return $this->aliasMapping;
}
public function getEnableDisplayAll()
{
return $this->enableDisplayAll;
}
//-------------protected functionS-------------------
/**
* @param $value
*/
protected function handleiDisplayStart($value)
{
//skip
$this->skip($value);
}
/**
* @param $value
*/
protected function handleiDisplayLength($value)
{
//limit nicht am query, sondern den ganzen
//holen und dann dynamisch in der Collection taken und skippen
// fix dispaly all when iDisplayLength choosed unlimit.
if(is_numeric($value)){
if($value > -1){
$this->take($value);
return;// jmp
}else if($value == -1 && $this->enableDisplayAll){
// Display All.
return;// jmp
}
}
// iDisplayLength invalid!
$this->take(isset($this->config['defaultDisplayLength'])? $this->config['defaultDisplayLength'] : 10);
}
/**
* @param $value
*/
protected function handlesEcho($value)
{
$this->sEcho = $value;
}
/**
* @param $value
*/
protected function handlesSearch($value)
{
//handle search on columns sSearch, bRegex
$this->search($value);
}
/**
* @param $value
*/
protected function handleiSortCol_0($value)
{
if(Input::get('sSortDir_0') == 'desc')
$direction[$value] = BaseEngine::ORDER_DESC;
else
$direction[$value] = BaseEngine::ORDER_ASC;
$columns = array();
//check if order is allowed
if(empty($this->orderColumns))
{
$columns[] = array(0 => $value, 1 => '`'.$this->getNameByIndex($value).'`');
$this->order($columns, $direction);
return;
}
//prepare order array
$cleanNames = array();
foreach($this->orderColumns as $c)
{
if(strpos($c,':') !== FALSE)
{
$cleanNames[] = substr($c, 0, strpos($c,':'));
}
else
{
$cleanNames[] = $c;
}
}
$iSortingCols = Input::get('iSortingCols');
$sortingCols[] = $value;
for($i = 1; $i < $iSortingCols; $i++) {
$isc = Input::get('iSortCol_'.$i);
$sortingCols[] = $isc;
$direction[$isc] = Input::get('sSortDir_'.$i);
}
$allColumns = array_keys($this->columns->all());
foreach ($sortingCols as $num) {
if(isset($allColumns[$num]) && in_array($allColumns[$num], $cleanNames)) {
$columns[] = array(0 => $num, 1 => '`'.$this->orderColumns[array_search($allColumns[$num],$cleanNames)].'`');
}
}
$this->order($columns, $direction);
return;
}
/**
* @param int $columnIndex
* @param string $searchValue
*
* @return void
*/
protected function handleSingleColumnSearch($columnIndex, $searchValue)
{
if (!isset($this->searchColumns[$columnIndex])) return;
if (empty($searchValue) && $searchValue !== '0') return;
$columnName = $this->searchColumns[$columnIndex];
$this->searchOnColumn($columnName, $searchValue);
}
/**
*
*/
protected function handleInputs()
{
//Handle all inputs magically
foreach (Input::all() as $key => $input) {
// handle single column search
if ($this->isParameterForSingleColumnSearch($key))
{
$columnIndex = str_replace('sSearch_','',$key);
$this->handleSingleColumnSearch($columnIndex, $input);
continue;
}
if(method_exists($this, $function = 'handle'.$key))
$this->$function($input);
}
}
/**
* @param $parameterName
*
* @return bool
*/
protected function isParameterForSingleColumnSearch($parameterName)
{
static $parameterNamePrefix = 'sSearch_';
return str_contains($parameterName, $parameterNamePrefix);
}
protected function prepareSearchColumns()
{
if(count($this->searchColumns) == 0 || empty($this->searchColumns))
$this->searchColumns = $this->showColumns;
}
/**
* @param $column
* @param $order
*/
protected function order($column, $order = BaseEngine::ORDER_ASC)
{
$this->orderColumn = $column;
$this->orderDirection = $order;
}
/**
* @param $value
*/
protected function search($value)
{
$this->search = $value;
}
/**
* @param string $columnName
* @param mixed $value
*/
protected function searchOnColumn($columnName, $value)
{
$this->fieldSearches[] = $columnName;
$this->columnSearches[] = $value;
}
/**
* @param $value
*/
protected function skip($value)
{
$this->skip = $value;
}
/**
* @param $value
*/
protected function take($value)
{
$this->limit = $value;
}
public function getNameByIndex($index)
{
$i = 0;
foreach($this->columns as $name => $col)
{
if($index == $i)
{
return $name;
}
$i++;
}
}
public function getExactWordSearch()
{
return $this->exactWordSearch;
}
abstract protected function totalCount();
abstract protected function count();
abstract protected function internalMake(Collection $columns, array $searchColumns = array());
}

View File

@@ -0,0 +1,306 @@
<?php namespace Chumper\Datatable\Engines;
use Illuminate\Support\Collection;
/**
* This handles the collections,
* it needs to compile first, so we wait for the make command and then
* do all the operations
*
* Class CollectionEngine
* @package Chumper\Datatable\Engines
*/
class CollectionEngine extends BaseEngine {
/**
* @var \Illuminate\Support\Collection
*/
private $workingCollection;
/**
* @var \Illuminate\Support\Collection
*/
private $collection;
/**
* @var string
*/
const OR_CONDITION = 'OR';
/**
* @var string
*/
const AND_CONDITION = 'AND';
/**
* @var array Different options
*/
private $options = array(
'stripOrder' => false,
'stripSearch' => false,
'caseSensitive' => false,
);
/**
* @param Collection $collection
*/
function __construct(Collection $collection)
{
parent::__construct();
$this->collection = $collection;
$this->workingCollection = $collection;
}
/**
* @return int
*/
public function count()
{
return $this->workingCollection->count();
}
/**
* @return int
*/
public function totalCount()
{
return $this->collection->count();
}
/**
* @return array
*/
public function getArray()
{
$this->handleInputs();
$this->compileArray($this->columns);
$this->doInternalSearch(new Collection(), array());
$this->doInternalOrder();
return array_values($this->workingCollection
->slice($this->skip,$this->limit)
->toArray()
);
}
/**
* Resets all operations performed on the collection
*/
public function reset()
{
$this->workingCollection = $this->collection;
return $this;
}
public function stripSearch()
{
$this->options['stripSearch'] = true;
return $this;
}
public function stripOrder()
{
$this->options['stripOrder'] = true;
return $this;
}
public function setSearchStrip()
{
$this->options['stripSearch'] = true;
return $this;
}
public function setOrderStrip()
{
$this->options['stripOrder'] = true;
return $this;
}
public function setCaseSensitive($value)
{
$this->options['caseSensitive'] = $value;
return $this;
}
public function getOption($value)
{
return $this->options[$value];
}
//--------------PRIVATE FUNCTIONS-----------------
protected function internalMake(Collection $columns, array $searchColumns = array())
{
$this->compileArray($columns);
$this->doInternalSearch($columns, $searchColumns);
$this->doInternalOrder();
return $this->workingCollection->slice($this->skip,$this->limit)->values();
}
private function doInternalSearch(Collection $columns, array $searchColumns)
{
if((is_null($this->search) || empty($this->search)) && empty($this->fieldSearches))
return;
$value = $this->search;
$caseSensitive = $this->options['caseSensitive'];
$toSearch = array();
$searchType = self::AND_CONDITION;
// Map the searchColumns to the real columns
$ii = 0;
foreach($columns as $i => $col)
{
if(in_array($columns->get($i)->getName(), $searchColumns) || in_array($columns->get($i)->getName(), $this->fieldSearches))
{
// map values to columns, where there is no value use the global value
if(($field = array_search($columns->get($i)->getName(), $this->fieldSearches)) !== FALSE)
{
$toSearch[$ii] = $this->columnSearches[$field];
}
else
{
if($value)
$searchType = self::OR_CONDITION;
$toSearch[$ii] = $value;
}
}
$ii++;
}
$self = $this;
$this->workingCollection = $this->workingCollection->filter(function($row) use ($toSearch, $caseSensitive, $self, $searchType)
{
for($i=0, $stack=array(), $nb=count($row); $i<$nb; $i++)
{
if(!array_key_exists($i, $toSearch))
continue;
$column = $i;
if($self->getAliasMapping())
{
$column = $self->getNameByIndex($i);
}
if($self->getOption('stripSearch'))
{
$search = strip_tags($row[$column]);
}
else
{
$search = $row[$column];
}
if($caseSensitive)
{
if($self->exactWordSearch)
{
if($toSearch[$i] === $search)
$stack[$i] = true;
}
else
{
if(str_contains($search,$toSearch[$i]))
$stack[$i] = true;
}
}
else
{
if($self->getExactWordSearch())
{
if(mb_strtolower($toSearch[$i]) === mb_strtolower($search))
$stack[$i] = true;
}
else
{
if(str_contains(mb_strtolower($search),mb_strtolower($toSearch[$i])))
$stack[$i] = true;
}
}
}
if($searchType == $self::AND_CONDITION)
{
$result = array_diff_key(array_filter($toSearch), $stack);
if(empty($result))
return true;
}
else
{
if(!empty($stack))
return true;
}
});
}
private function doInternalOrder()
{
if(is_null($this->orderColumn))
return;
// Bug added on pull request #309
$column = array_values($this->orderColumn)[0];
$direction = array_values($this->orderDirection)[0];
$stripOrder = $this->options['stripOrder'];
$sortFunction = 'sortBy';
if ($direction == BaseEngine::ORDER_DESC)
$sortFunction = 'sortByDesc';
$this->workingCollection->{$sortFunction}(function($row) use ($column,$stripOrder) {
if($this->getAliasMapping())
{
$column = $this->getNameByIndex($column[0]);
return $row[$column];
}
if($stripOrder)
{
return strip_tags($row[$column]);
}
else
{
if (is_array($column))
return $row[$column[0]];
return $row[$column];
}
});
}
private function compileArray($columns)
{
$self = $this;
$this->workingCollection = $this->collection->map(function($row) use ($columns, $self) {
$entry = array();
// add class and id if needed
if(!is_null($self->getRowClass()) && is_callable($self->getRowClass()))
{
$entry['DT_RowClass'] = call_user_func($self->getRowClass(),$row);
}
if(!is_null($self->getRowId()) && is_callable($self->getRowId()))
{
$entry['DT_RowId'] = call_user_func($self->getRowId(),$row);
}
$i=0;
foreach ($columns as $col)
{
if($self->getAliasMapping())
{
$entry[$col->getName()] = $col->run($row);
}
else
{
$entry[$i] = $col->run($row);
}
$i++;
}
return $entry;
});
}
}

View File

@@ -0,0 +1,383 @@
<?php namespace Chumper\Datatable\Engines;
use \Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Collection;
class QueryEngine extends BaseEngine {
/**
* @var \Illuminate\Database\Query\Builder
*/
public $builder;
/**
* @var \Illuminate\Database\Query\Builder
*/
public $originalBuilder;
/**
* @var Collection the returning collection
*/
private $resultCollection;
/**
* @var Collection the resulting collection
*/
private $collection = null;
/**
* @var array Different options
*/
private $options = array(
'searchOperator' => 'LIKE',
'searchWithAlias' => false,
'orderOrder' => null,
'counter' => 0,
'noGroupByOnCount' => false,
'distinctCountGroup'=> false,
'emptyAtEnd' => false,
'returnQuery' => false,
'queryKeepsLimits' => false,
);
function __construct($builder)
{
parent::__construct();
if($builder instanceof Relation)
{
$this->builder = $builder->getBaseQuery();
$this->originalBuilder = clone $builder->getBaseQuery();
}
else
{
$this->builder = $builder;
$this->originalBuilder = clone $builder;
}
}
public function count()
{
return $this->options['counter'];
}
public function totalCount()
{
if ($this->options['distinctCountGroup'] && count($this->originalBuilder->groups) == 1)
{
$this->originalBuilder->groups = null;
}
if($this->options['searchWithAlias']) {
$cnt = count($this->originalBuilder->get());
} else {
$cnt = $this->originalBuilder->count();
}
return $cnt;
}
public function getArray()
{
return $this->getCollection($this->builder)->toArray();
}
public function reset()
{
$this->builder = $this->originalBuilder;
return $this;
}
public function setSearchOperator($value = "LIKE")
{
$this->options['searchOperator'] = $value;
return $this;
}
public function setSearchWithAlias($value = true)
{
$this->options['searchWithAlias'] = (bool)$value;
return $this;
}
public function setEmptyAtEnd()
{
$this->options['emptyAtEnd'] = true;
return $this;
}
public function setNoGroupByOnCount($value = true)
{
$this->options['noGroupByOnCount'] = (bool)$value;
return $this;
}
/**
* Change the COUNT(*) when there is a group by
*
* setDistinctIfGroup will change the count(*) query inside the query builder if it only finds one group by.
*
* Instead of counting all of the rows, the distinct rows in the group by will be counted instead.
*
* @param bool $value should this option be enabled?
* @return $this
*/
public function setDistinctCountGroup($value = true)
{
$this->options['distinctCountGroup'] = (bool)$value;
return $this;
}
/**
* Let internalMake return a QueryBuilder, instead of a collection.
*
* @param bool $value
* @return $this
*/
public function setReturnQuery($value = true)
{
$this->options['returnQuery'] = $value;
return $this;
}
/**
* Allow setting an array of options on the QueryEngine without needing to run each setter.
*
* @param array $options
* @return $this
* @throws Exception
*/
public function setOptions($options = array())
{
foreach($options as $option_name => $option_value)
{
if (!isset($this->options[$option_name]))
throw new Exception("The option $option_name is not a valid that can be selected.");
if (is_bool($this->options[$option_name]))
$option_value = (bool)$option_value;
$this->options[$option_name] = $option_value;
}
return $this;
}
/**
* Change the behaviour of getQueryBuiler for limits
*
* @param bool $value
* @return $this
*/
public function setQueryKeepsLimits($value = true)
{
$this->options['queryKeepsLimits'] = $value;
return $this;
}
/**
* Get a Builder object back from the engine. Don't return a collection.
*
* @return Query\Builder
*/
public function getQueryBuilder()
{
$this->prepareEngine();
$this->setReturnQuery();
return $this->internalMake($this->columns, $this->searchColumns);
}
//--------PRIVATE FUNCTIONS
protected function internalMake(Collection $columns, array $searchColumns = array())
{
$builder = clone $this->builder;
$countBuilder = clone $this->builder;
$builder = $this->doInternalSearch($builder, $searchColumns);
$countBuilder = $this->doInternalSearch($countBuilder, $searchColumns);
if ($this->options['distinctCountGroup'] && count($countBuilder->groups) == 1)
{
$countBuilder->select(\DB::raw('COUNT(DISTINCT `' . $countBuilder->groups[0] . '`) as total'));
$countBuilder->groups = null;
$results = $countBuilder->get('rows');
if (isset($results[0]))
{
$result = array_change_key_case((array) $results[0]);
}
$this->options['counter'] = $result['total'];
}
elseif($this->options['searchWithAlias'])
{
$this->options['counter'] = count($countBuilder->get());
}
else
{
if ($this->options['noGroupByOnCount']) {
$countBuilder->groups = null;
}
$this->options['counter'] = $countBuilder->count();
}
$builder = $this->doInternalOrder($builder, $columns);
if ($this->options['returnQuery'])
if ($this->options['queryKeepsLimits'])
return $this->getQuery($builder);
else
return $builder;
$collection = $this->compile($builder, $columns);
return $collection;
}
/**
* @param $builder
* @return Collection
*/
private function getQuery($builder)
{
if (is_null($this->collection)) {
if ($this->skip > 0) {
$builder = $builder->skip($this->skip);
}
if ($this->limit > 0) {
$builder = $builder->take($this->limit);
}
}
return $builder;
}
private function getCollection($builder)
{
$builder = $this->getQuery($builder);
if (is_null($this->collection))
{
$this->collection = $builder->get();
if(is_array($this->collection))
$this->collection = new Collection($this->collection);
}
return $this->collection;
}
private function doInternalSearch($builder, $columns)
{
if (!empty($this->search)) {
$builder = $this->buildSearchQuery($builder, $columns);
}
if (!empty($this->columnSearches)) {
$builder = $this->buildSingleColumnSearches($builder);
}
return $builder;
}
private function buildSearchQuery($builder, $columns)
{
$like = $this->options['searchOperator'];
$search = $this->search;
$exact = $this->exactWordSearch;
$builder = $builder->where(function($query) use ($columns, $search, $like, $exact) {
foreach ($columns as $c) {
//column to CAST following the pattern column:newType:[maxlength]
if(strrpos($c, ':')){
$c = explode(':', $c);
if(isset($c[2]))
$c[1] .= "($c[2])";
$query->orWhereRaw("cast($c[0] as $c[1]) ".$like." ?", array($exact ? "$search" : "%$search%"));
}
else
$query->orWhere($c,$like,$exact ? $search : '%'.$search.'%');
}
});
return $builder;
}
/**
* @param $builder
* Modified by sburkett to facilitate individual exact match searching on individual columns (rather than for all columns)
*/
private function buildSingleColumnSearches(Builder $builder)
{
foreach ($this->columnSearches as $index => $searchValue) {
$fieldSearchIndex = $this->fieldSearches[$index];
if (isset($this->columnSearchExact[$fieldSearchIndex])
&& $this->columnSearchExact[$fieldSearchIndex] == 1)
{
$builder->where($fieldSearchIndex, '=', $searchValue);
} else {
$builder->where($fieldSearchIndex, $this->options['searchOperator'], '%' . $searchValue . '%');
}
}
}
private function compile($builder, $columns)
{
$this->resultCollection = $this->getCollection($builder);
$self = $this;
$this->resultCollection = $this->resultCollection->map(function($row) use ($columns,$self) {
$entry = array();
// add class and id if needed
if(!is_null($self->getRowClass()) && is_callable($self->getRowClass()))
{
$entry['DT_RowClass'] = call_user_func($self->getRowClass(),$row);
}
if(!is_null($self->getRowId()) && is_callable($self->getRowId()))
{
$entry['DT_RowId'] = call_user_func($self->getRowId(),$row);
}
if(!is_null($self->getRowData()) && is_callable($self->getRowData()))
{
$entry['DT_RowData'] = call_user_func($self->getRowData(),$row);
}
$i = 0;
foreach ($columns as $col)
{
if($self->getAliasMapping())
{
$entry[$col->getName()] = $col->run($row);
}
else
{
$entry[$i] = $col->run($row);
}
$i++;
}
return $entry;
});
return $this->resultCollection;
}
private function doInternalOrder($builder, $columns)
{
if(!is_null($this->orderColumn))
{
foreach ($this->orderColumn as $ordCol) {
if(strrpos($ordCol[1], ':')){
$c = explode(':', $ordCol[1]);
if(isset($c[2]))
$c[1] .= "($c[2])";
$prefix = $this->options['emptyAtEnd'] ? "ISNULL({$c[0]}) asc," : '';
$builder = $builder->orderByRaw($prefix." cast($c[0] as $c[1]) ".$this->orderDirection[$ordCol[0]]);
}
else {
$prefix = $this->options['emptyAtEnd'] ? "ISNULL({$ordCol[1]}) asc," : '';
$builder = $builder->orderByRaw($prefix.' '.$ordCol[1].' '.$this->orderDirection[$ordCol[0]]);
}
}
}
return $builder;
}
}

View File

@@ -0,0 +1,14 @@
<?php namespace Chumper\Datatable\Facades;
use Illuminate\Support\Facades\Facade;
class DatatableFacade extends Facade {
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor() { return 'datatable'; }
}

View File

@@ -0,0 +1,441 @@
<?php namespace Chumper\Datatable;
use Exception;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Config;
/**
* Class Table
* @package Chumper\Datatable
*/
class Table {
/**
* @var array
*/
private $config = array();
/**
* @var array
*/
private $columns = array();
/**
* @var array
*/
private $options = array();
/**
* @var array
*/
private $callbacks = array();
/**
* Values to be sent to custom templates
*
* @var array
*/
private $customValues = array();
/**
* @var array
*/
private $data = array();
/**
* @var boolean Determines if the template should echo the javascript
*/
private $noScript = false;
/**
* @var String The name of the id the table will have later
*/
protected $idName;
/**
* @var String The name of the class the table will have later
*/
protected $className;
/**
* @var String The view used to render the table
*/
protected $table_view;
/**
* @var String The view used to render the javascript
*/
protected $script_view;
/**
* @var boolean indicates if the mapping was already added to the options
*/
private $createdMapping = true;
/**
* @var array name of mapped columns
*/
private $aliasColumns = array();
function __construct()
{
$this->config = Config::get('chumper.datatable.table');
$this->setId( $this->config['id'] );
$this->setClass( $this->config['class'] );
$this->setOptions( $this->config['options'] );
$this->setCallbacks( $this->config['callbacks'] );
$this->noScript = $this->config['noScript'];
$this->table_view = $this->config['table_view'];
$this->script_view = $this->config['script_view'];
}
/**
* @return $this
*/
public function addColumn()
{
foreach (func_get_args() as $title)
{
if(is_array($title))
{
foreach ($title as $mapping => $arrayTitle)
{
$this->columns[] = $arrayTitle;
$this->aliasColumns[] = $mapping;
if(is_string($mapping))
{
$this->createdMapping = false;
}
}
}
else
{
$this->columns[] = $title;
$this->aliasColumns[] = count($this->aliasColumns)+1;
}
}
return $this;
}
/**
* @return int
*/
public function countColumns()
{
return count($this->columns);
}
/**
* @return $this
*/
public function removeOption($key)
{
if(isset($this->options[$key])) unset($this->options[$key]);
return $this;
}
/**
* @return $this
* @throws \Exception
*/
public function setOptions()
{
if(func_num_args() == 2)
{
$this->options[func_get_arg(0)] =func_get_arg(1);
}
else if(func_num_args() == 1 && is_array(func_get_arg(0)))
{
foreach (func_get_arg(0) as $key => $option)
{
$this->options[$key] = $option;
}
}
else
throw new Exception('Invalid number of options provided for the method "setOptions"');
return $this;
}
/**
* @return $this
* @throws \Exception
*/
public function setOrder($order = array())
{
$_orders = array();
foreach ($order as $number => $sort)
{
$_orders[] = [$number, $sort];
}
$this->callbacks['aaSorting'] = $_orders;
return $this;
}
/**
* @return $this
* @throws \Exception
*/
public function setCallbacks()
{
if(func_num_args() == 2)
{
$this->callbacks[func_get_arg(0)] = func_get_arg(1);
}
else if(func_num_args() == 1 && is_array(func_get_arg(0)))
{
foreach (func_get_arg(0) as $key => $value)
{
$this->callbacks[$key] = $value;
}
}
else
throw new Exception('Invalid number of callbacks provided for the method "setCallbacks"');
return $this;
}
/**
* @return $this
* @throws \Exception
*/
public function setCustomValues()
{
if(func_num_args() == 2)
{
$this->customValues[func_get_arg(0)] = func_get_arg(1);
}
else if(func_num_args() == 1 && is_array(func_get_arg(0)))
{
foreach (func_get_arg(0) as $key => $value)
{
$this->customValues[$key] = $value;
}
}
else
throw new Exception('Invalid number of custom values provided for the method "setCustomValues"');
return $this;
}
/**
* @param array $data
* @return $this
*/
public function setData(array $data)
{
$this->data = $data;
return $this;
}
/**
* @param $url
* @return $this
*/
public function setUrl($url)
{
$this->options['sAjaxSource'] = $url;
$this->options['bServerSide'] = true;
return $this;
}
/**
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* @return array
*/
public function getCallbacks()
{
return $this->callbacks;
}
/**
* @return array
*/
public function getCustomValues()
{
return $this->customValues;
}
/**
* @return array
*/
public function getData()
{
return $this->data;
}
/**
* @param null $view
* @return mixed
*/
public function render($view = null)
{
if( ! is_null($view))
$this->table_view = $view;
return View::make($this->table_view, $this->getViewParameters());
}
/**
* returns an array with the parameters that will be passed to the view when it's rendered
* @return array
*/
public function getViewParameters()
{
if(!isset($this->options['sAjaxSource']))
{
$this->setUrl(Request::url());
}
// create mapping for frontend
if(!$this->createdMapping)
{
$this->createMapping();
}
return array(
'options' => $this->convertData(array_merge($this->options, $this->callbacks)),
'values' => $this->customValues,
'data' => $this->data,
'columns' => array_combine($this->aliasColumns,$this->columns),
'noScript' => $this->noScript,
'id' => $this->idName,
'class' => $this->className,
);
}
/**
* Instructs the table not to echo the javascript
*
* @return $this
*/
public function noScript()
{
$this->noScript = true;
return $this;
}
private function convertData($options) {
$is_obj = false;
$first = true;
$data = "";
foreach ($options as $k => $o) {
if ($first == true) {
if (!is_numeric($k)) {
$is_obj = true;
}
$first = false;
} else {
$data .= ",\n";
}
if (!is_numeric($k)) {
$data .= json_encode($k) . ":";
}
if (is_string($o)) {
if (@preg_match("#^\s*function\s*\([^\)]*#", $o)) {
$data .= $o;
} else {
$data .= json_encode($o);
}
} else {
if (is_array($o)) {
$data .= $this->convertData($o);
} else {
$data .= json_encode($o);
}
}
}
if ($is_obj) {
$data = "{ $data }";
} else {
$data = "[ $data ]";
}
return $data;
}
public function script($view = null)
{
if( ! is_null($view))
$this->script_view = $view;
// create mapping for frontend
if(!$this->createdMapping)
{
$this->createMapping();
}
return View::make($this->script_view,array(
'options' => $this->convertData(array_merge($this->options, $this->callbacks)),
'id' => $this->idName,
));
}
public function getId()
{
return $this->idName;
}
public function setId($id = '')
{
$this->idName = empty($id)? str_random(8) : $id;
return $this;
}
public function getClass()
{
return $this->className;
}
public function setClass($class)
{
$this->className = $class;
return $this;
}
public function setAliasMapping($value)
{
$this->createdMapping = !$value;
return $this;
}
//--------------------PRIVATE FUNCTIONS
private function createMapping()
{
// set options for better handling
// merge with existing options
if(!array_key_exists('aoColumns', $this->options))
{
$this->options['aoColumns'] = array();
}
$matching = array();
$i = 0;
foreach($this->aliasColumns as $name)
{
if(array_key_exists($i,$this->options['aoColumns']))
{
$this->options['aoColumns'][$i] = array_merge_recursive($this->options['aoColumns'][$i],array('mData' => $name));
}
else
{
$this->options['aoColumns'][$i] = array('mData' => $name);
}
$i++;
}
$this->createdMapping = true;
//dd($matching);
return $matching;
}
}

View File

View File

@@ -0,0 +1,155 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Table specific configuration options.
|--------------------------------------------------------------------------
|
*/
'table' => array(
/*
|--------------------------------------------------------------------------
| Table class
|--------------------------------------------------------------------------
|
| Class(es) added to the table
| Supported: string
|
*/
'class' => 'table table-bordered',
/*
|--------------------------------------------------------------------------
| Table ID
|--------------------------------------------------------------------------
|
| ID given to the table. Used for connecting the table and the Datatables
| jQuery plugin. If left empty a random ID will be generated.
| Supported: string
|
*/
'id' => '',
/*
|--------------------------------------------------------------------------
| DataTable options
|--------------------------------------------------------------------------
|
| jQuery dataTable plugin options. The array will be json_encoded and
| passed through to the plugin. See https://datatables.net/usage/options
| for more information.
| Supported: array
|
*/
'options' => array(
"sPaginationType" => "full_numbers",
"bProcessing" => false
),
/*
|--------------------------------------------------------------------------
| DataTable callbacks
|--------------------------------------------------------------------------
|
| jQuery dataTable plugin callbacks. The array will be json_encoded and
| passed through to the plugin. See https://datatables.net/usage/callbacks
| for more information.
| Supported: array
|
*/
'callbacks' => array(),
/*
|--------------------------------------------------------------------------
| Skip javascript in table template
|--------------------------------------------------------------------------
|
| Determines if the template should echo the javascript
| Supported: boolean
|
*/
'noScript' => false,
/*
|--------------------------------------------------------------------------
| Table view
|--------------------------------------------------------------------------
|
| Template used to render the table
| Supported: string
|
*/
'table_view' => 'datatable::template',
/*
|--------------------------------------------------------------------------
| Script view
|--------------------------------------------------------------------------
|
| Template used to render the javascript
| Supported: string
|
*/
'script_view' => 'datatable::javascript',
),
/*
|--------------------------------------------------------------------------
| Engine specific configuration options.
|--------------------------------------------------------------------------
|
*/
'engine' => array(
/*
|--------------------------------------------------------------------------
| Search for exact words
|--------------------------------------------------------------------------
|
| If the search should be done with exact matching
| Supported: boolean
|
*/
'exactWordSearch' => false,
/*
|--------------------------------------------------------------------------
| Enable to display all records.
|--------------------------------------------------------------------------
|
| Be careful! It may be overloaded with large record.
| Supported: boolean
|
*/
'enableDisplayAll' => false,
/*
|--------------------------------------------------------------------------
| Limit display when iDisplayLength invaild
|--------------------------------------------------------------------------
*/
'defaultDisplayLength' => 10,
)
);

View File

@@ -0,0 +1,32 @@
<script type="text/javascript">
jQuery(document).ready(function(){
// dynamic table
oTable = jQuery('#{{ $id }}').dataTable({
@foreach ($options as $k => $o)
{{ json_encode($k) }}: @if(!is_array($o)) @if(preg_match("/function/", $o)) {{ $o }}, @else {{ json_encode($o) }}, @endif
@elseif(key($o) === 0) {{-- if we have an array, no need to print keys --}}
[
@foreach ($o as $r)
@if(is_array($r)) {{ json_encode($r) }}, @elseif(preg_match("/function/", $r)) {{ $r }}, @else {{ json_encode($r) }}, @endif
@endforeach
],
@else
{
@foreach ($o as $x => $r)
{{ json_encode($x) }}: @if(is_array($r)) {{ json_encode($r) }}, @elseif(preg_match("/function/", $r)) {{ $r }}, @else {{ json_encode($r) }}, @endif
@endforeach
},
@endif
@endforeach
@foreach ($callbacks as $k => $o)
{{ json_encode($k) }}: {{ $o }},
@endforeach
});
// custom values are available via $values array
});
</script>

View File

@@ -0,0 +1,36 @@
<table id="{{ $id }}" class="{{ $class }}">
<colgroup>
@for ($i = 0; $i < count($columns); $i++)
<col class="con{{ $i }}" />
@endfor
</colgroup>
<thead>
<tr>
@foreach($columns as $i => $c)
<th align="center" valign="middle" class="head{{ $i }}">{{ $c }}</th>
@endforeach
</tr>
</thead>
@if ($footerMode !== 'hidden')
<tfoot>
<tr>
@foreach($columns as $i => $c)
<th align="center" valign="middle" class="footer{{ $i }}">@if($footerMode === 'columns') {{ $c }} @endif</th>
@endforeach
</tr>
</tfoot>
@endif
<tbody>
@foreach($data as $d)
<tr>
@foreach($d as $dd)
<td>{{ $dd }}</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
@if (!$noScript)
@include(Config::get('datatable::table.script_view'), array('id' => $id, 'options' => $options, 'callbacks' => $callbacks))
@endif