Файловый менеджер - Редактировать - /home/easybachat/hisabat365.com/4a7891/laravel-datatables-oracle.tar
Ðазад
sonar-project.properties 0000644 00000000143 15060250030 0011435 0 ustar 00 # Define separate root directories for sources and tests sonar.sources = src/ sonar.tests = tests/ src/QueryDataTable.php 0000644 00000071462 15060250030 0010714 0 ustar 00 <?php namespace Yajra\DataTables; use Illuminate\Contracts\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Contracts\Database\Query\Builder as QueryBuilder; use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Query\Expression; use Illuminate\Http\JsonResponse; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; use Yajra\DataTables\Utilities\Helper; class QueryDataTable extends DataTableAbstract { /** * Flag for ordering NULLS LAST option. */ protected bool $nullsLast = false; /** * Flag to check if query preparation was already done. */ protected bool $prepared = false; /** * Query callback for custom pagination using limit without offset. * * @var callable|null */ protected $limitCallback = null; /** * Flag to keep the select bindings. */ protected bool $keepSelectBindings = false; /** * Flag to ignore the selects in count query. */ protected bool $ignoreSelectInCountQuery = false; /** * Enable scout search and use this model for searching. */ protected ?Model $scoutModel = null; /** * Maximum number of hits to return from scout. */ protected int $scoutMaxHits = 1000; /** * Add dynamic filters to scout search. * * @var callable|null */ protected $scoutFilterCallback = null; /** * Flag if scout search was performed. */ protected bool $scoutSearched = false; /** * Scout index name. */ protected string $scoutIndex; /** * Scout key name. */ protected string $scoutKey; /** * Flag to disable user ordering if a fixed ordering was performed (e.g. scout search). * Only works with corresponding javascript listener. */ protected bool $disableUserOrdering = false; public function __construct(protected QueryBuilder $query) { $this->request = app('datatables.request'); $this->config = app('datatables.config'); $this->columns = $this->query->getColumns(); if ($this->config->isDebugging()) { $this->getConnection()->enableQueryLog(); } } public function getConnection(): Connection { /** @var Connection $connection */ $connection = $this->query->getConnection(); return $connection; } /** * Can the DataTable engine be created with these parameters. * * @param mixed $source */ public static function canCreate($source): bool { return $source instanceof QueryBuilder && ! ($source instanceof EloquentBuilder); } /** * Organizes works. * * @throws \Exception */ public function make(bool $mDataSupport = true): JsonResponse { try { $results = $this->prepareQuery()->results(); $processed = $this->processResults($results, $mDataSupport); $data = $this->transform($results, $processed); return $this->render($data); } catch (\Exception $exception) { return $this->errorResponse($exception); } } /** * Get paginated results. * * @return \Illuminate\Support\Collection<int, array> */ public function results(): Collection { return $this->query->get(); } /** * Prepare query by executing count, filter, order and paginate. * * @return $this */ public function prepareQuery(): static { if (! $this->prepared) { $this->totalRecords = $this->totalCount(); $this->filterRecords(); $this->ordering(); $this->paginate(); } $this->prepared = true; return $this; } /** * Counts current query. */ public function count(): int { return $this->prepareCountQuery()->count(); } /** * Prepare count query builder. */ public function prepareCountQuery(): QueryBuilder { $builder = clone $this->query; if ($this->isComplexQuery($builder)) { $builder->select(DB::raw('1 as dt_row_count')); $clone = $builder->clone(); $clone->setBindings([]); if ($clone instanceof EloquentBuilder) { $clone->getQuery()->wheres = []; } else { $clone->wheres = []; } if ($this->isComplexQuery($clone)) { if (! $this->ignoreSelectInCountQuery) { $builder = clone $this->query; } return $this->getConnection() ->query() ->fromRaw('('.$builder->toSql().') count_row_table') ->setBindings($builder->getBindings()); } } $row_count = $this->wrap('row_count'); $builder->select($this->getConnection()->raw("'1' as {$row_count}")); if (! $this->keepSelectBindings) { $builder->setBindings([], 'select'); } return $builder; } /** * Check if builder query uses complex sql. * * @param QueryBuilder|EloquentBuilder $query */ protected function isComplexQuery($query): bool { return Str::contains(Str::lower($query->toSql()), ['union', 'having', 'distinct', 'order by', 'group by']); } /** * Wrap column with DB grammar. */ protected function wrap(string $column): string { return $this->getConnection()->getQueryGrammar()->wrap($column); } /** * Keep the select bindings. * * @return $this */ public function keepSelectBindings(): static { $this->keepSelectBindings = true; return $this; } /** * Perform column search. */ protected function filterRecords(): void { $initialQuery = clone $this->query; if ($this->autoFilter && $this->request->isSearchable()) { $this->filtering(); } if (is_callable($this->filterCallback)) { call_user_func_array($this->filterCallback, $this->resolveCallbackParameter()); } $this->columnSearch(); $this->searchPanesSearch(); // If no modification between the original query and the filtered one has been made // the filteredRecords equals the totalRecords if ($this->query == $initialQuery) { $this->filteredRecords ??= $this->totalRecords; } else { $this->filteredCount(); } } /** * Perform column search. */ public function columnSearch(): void { $columns = $this->request->columns(); foreach ($columns as $index => $column) { $column = $this->getColumnName($index); if (is_null($column)) { continue; } if (! $this->request->isColumnSearchable($index) || $this->isBlacklisted($column) && ! $this->hasFilterColumn($column)) { continue; } if ($this->hasFilterColumn($column)) { $keyword = $this->getColumnSearchKeyword($index, true); $this->applyFilterColumn($this->getBaseQueryBuilder(), $column, $keyword); } else { $column = $this->resolveRelationColumn($column); $keyword = $this->getColumnSearchKeyword($index); $this->compileColumnSearch($index, $column, $keyword); } } } /** * Check if column has custom filter handler. */ public function hasFilterColumn(string $columnName): bool { return isset($this->columnDef['filter'][$columnName]); } /** * Get column keyword to use for search. */ protected function getColumnSearchKeyword(int $i, bool $raw = false): string { $keyword = $this->request->columnKeyword($i); if ($raw || $this->request->isRegex($i)) { return $keyword; } return $this->setupKeyword($keyword); } protected function getColumnNameByIndex(int $index): string { $name = (isset($this->columns[$index]) && $this->columns[$index] != '*') ? $this->columns[$index] : $this->getPrimaryKeyName(); if ($name instanceof Expression) { $name = $name->getValue($this->query->getGrammar()); } return in_array($name, $this->extraColumns, true) ? $this->getPrimaryKeyName() : $name; } /** * Apply filterColumn api search. * * @param QueryBuilder $query */ protected function applyFilterColumn($query, string $columnName, string $keyword, string $boolean = 'and'): void { $query = $this->getBaseQueryBuilder($query); $callback = $this->columnDef['filter'][$columnName]['method']; if ($this->query instanceof EloquentBuilder) { $builder = $this->query->newModelInstance()->newQuery(); } else { $builder = $this->query->newQuery(); } $callback($builder, $keyword); /** @var \Illuminate\Database\Query\Builder $baseQueryBuilder */ $baseQueryBuilder = $this->getBaseQueryBuilder($builder); $query->addNestedWhereQuery($baseQueryBuilder, $boolean); } /** * Get the base query builder instance. * * @param QueryBuilder|EloquentBuilder|null $instance */ protected function getBaseQueryBuilder($instance = null): QueryBuilder { if (! $instance) { $instance = $this->query; } if ($instance instanceof EloquentBuilder) { return $instance->getQuery(); } return $instance; } /** * Get query builder instance. */ public function getQuery(): QueryBuilder { return $this->query; } /** * Resolve the proper column name be used. */ protected function resolveRelationColumn(string $column): string { return $column; } /** * Compile queries for column search. */ protected function compileColumnSearch(int $i, string $column, string $keyword): void { if ($this->request->isRegex($i)) { $this->regexColumnSearch($column, $keyword); } else { $this->compileQuerySearch($this->query, $column, $keyword, ''); } } /** * Compile regex query column search. */ protected function regexColumnSearch(string $column, string $keyword): void { $column = $this->wrap($column); switch ($this->getConnection()->getDriverName()) { case 'oracle': $sql = ! $this->config->isCaseInsensitive() ? 'REGEXP_LIKE( '.$column.' , ? )' : 'REGEXP_LIKE( LOWER('.$column.') , ?, \'i\' )'; break; case 'pgsql': $column = $this->castColumn($column); $sql = ! $this->config->isCaseInsensitive() ? $column.' ~ ?' : $column.' ~* ? '; break; default: $sql = ! $this->config->isCaseInsensitive() ? $column.' REGEXP ?' : 'LOWER('.$column.') REGEXP ?'; $keyword = Str::lower($keyword); } $this->query->whereRaw($sql, [$keyword]); } /** * Wrap a column and cast based on database driver. */ protected function castColumn(string $column): string { return match ($this->getConnection()->getDriverName()) { 'pgsql' => 'CAST('.$column.' as TEXT)', 'firebird' => 'CAST('.$column.' as VARCHAR(255))', default => $column, }; } /** * Compile query builder where clause depending on configurations. * * @param QueryBuilder|EloquentBuilder $query */ protected function compileQuerySearch($query, string $column, string $keyword, string $boolean = 'or'): void { $column = $this->addTablePrefix($query, $column); $column = $this->castColumn($column); $sql = $column.' LIKE ?'; if ($this->config->isCaseInsensitive()) { $sql = 'LOWER('.$column.') LIKE ?'; } $query->{$boolean.'WhereRaw'}($sql, [$this->prepareKeyword($keyword)]); } /** * Patch for fix about ambiguous field. * Ambiguous field error will appear when query use join table and search with keyword. * * @param QueryBuilder|EloquentBuilder $query */ protected function addTablePrefix($query, string $column): string { if (! str_contains($column, '.')) { $q = $this->getBaseQueryBuilder($query); $from = $q->from ?? ''; if (! $from instanceof Expression) { if (str_contains((string) $from, ' as ')) { $from = explode(' as ', (string) $from)[1]; } $column = $from.'.'.$column; } } return $this->wrap($column); } /** * Prepare search keyword based on configurations. */ protected function prepareKeyword(string $keyword): string { if ($this->config->isCaseInsensitive()) { $keyword = Str::lower($keyword); } if ($this->config->isStartsWithSearch()) { return "$keyword%"; } if ($this->config->isWildcard()) { $keyword = Helper::wildcardLikeString($keyword); } if ($this->config->isSmartSearch()) { $keyword = "%$keyword%"; } return $keyword; } /** * Add custom filter handler for the give column. * * @param string $column * @return $this */ public function filterColumn($column, callable $callback): static { $this->columnDef['filter'][$column] = ['method' => $callback]; return $this; } /** * Order each given columns versus the given custom sql. * * @param string $sql * @param array $bindings * @return $this */ public function orderColumns(array $columns, $sql, $bindings = []): static { foreach ($columns as $column) { $this->orderColumn($column, str_replace(':column', $column, $sql), $bindings); } return $this; } /** * Override default column ordering. * * @param string $column * @param string|\Closure $sql * @param array $bindings * @return $this * * @internal string $1 Special variable that returns the requested order direction of the column. */ public function orderColumn($column, $sql, $bindings = []): static { $this->columnDef['order'][$column] = compact('sql', 'bindings'); return $this; } /** * Set datatables to do ordering with NULLS LAST option. * * @return $this */ public function orderByNullsLast(): static { $this->nullsLast = true; return $this; } /** * Perform pagination. */ public function paging(): void { $start = $this->request->start(); $length = $this->request->length(); $limit = $length > 0 ? $length : 10; if (is_callable($this->limitCallback)) { $this->query->limit($limit); call_user_func_array($this->limitCallback, [$this->query]); } else { $this->query->skip($start)->take($limit); } } /** * Paginate dataTable using limit without offset * with additional where clause via callback. * * @return $this */ public function limit(callable $callback): static { $this->limitCallback = $callback; return $this; } /** * Add column in collection. * * @param string $name * @param string|callable $content * @param bool|int $order * @return $this */ public function addColumn($name, $content, $order = false): static { $this->pushToBlacklist($name); return parent::addColumn($name, $content, $order); } /** * Perform search using search pane values. * * * @throws \Psr\Container\ContainerExceptionInterface * @throws \Psr\Container\NotFoundExceptionInterface */ protected function searchPanesSearch(): void { /** @var string[] $columns */ $columns = $this->request->get('searchPanes', []); foreach ($columns as $column => $values) { if ($this->isBlacklisted($column)) { continue; } if ($this->searchPanes[$column] && $callback = $this->searchPanes[$column]['builder']) { $callback($this->query, $values); } else { $this->query->whereIn($column, $values); } } } /** * Resolve callback parameter instance. * * @return array<int|string, mixed> */ protected function resolveCallbackParameter(): array { return [$this->query, $this->scoutSearched]; } /** * Perform default query orderBy clause. * * * @throws \Psr\Container\ContainerExceptionInterface * @throws \Psr\Container\NotFoundExceptionInterface */ protected function defaultOrdering(): void { collect($this->request->orderableColumns()) ->map(function ($orderable) { $orderable['name'] = $this->getColumnName($orderable['column'], true); return $orderable; }) ->reject(fn ($orderable) => $this->isBlacklisted($orderable['name']) && ! $this->hasOrderColumn($orderable['name'])) ->each(function ($orderable) { $column = $this->resolveRelationColumn($orderable['name']); if ($this->hasOrderColumn($orderable['name'])) { $this->applyOrderColumn($orderable['name'], $orderable); } elseif ($this->hasOrderColumn($column)) { $this->applyOrderColumn($column, $orderable); } else { $nullsLastSql = $this->getNullsLastSql($column, $orderable['direction']); $normalSql = $this->wrap($column).' '.$orderable['direction']; $sql = $this->nullsLast ? $nullsLastSql : $normalSql; $this->query->orderByRaw($sql); } }); } /** * Check if column has custom sort handler. */ protected function hasOrderColumn(string $column): bool { return isset($this->columnDef['order'][$column]); } /** * Apply orderColumn custom query. */ protected function applyOrderColumn(string $column, array $orderable): void { $sql = $this->columnDef['order'][$column]['sql']; if ($sql === false) { return; } if (is_callable($sql)) { call_user_func($sql, $this->query, $orderable['direction']); } else { $sql = str_replace('$1', $orderable['direction'], (string) $sql); $bindings = $this->columnDef['order'][$column]['bindings']; $this->query->orderByRaw($sql, $bindings); } } /** * Get NULLS LAST SQL. * * @param string $column * @param string $direction * * @throws \Psr\Container\ContainerExceptionInterface * @throws \Psr\Container\NotFoundExceptionInterface */ protected function getNullsLastSql($column, $direction): string { /** @var string $sql */ $sql = $this->config->get('datatables.nulls_last_sql', '%s %s NULLS LAST'); return str_replace( [':column', ':direction'], [$column, $direction], sprintf($sql, $column, $direction) ); } /** * Perform global search for the given keyword. */ protected function globalSearch(string $keyword): void { // Try scout search first & fall back to default search if disabled/failed if ($this->applyScoutSearch($keyword)) { return; } $this->query->where(function ($query) use ($keyword) { collect($this->request->searchableColumnIndex()) ->map(fn ($index) => $this->getColumnName($index)) ->filter() ->reject(fn ($column) => $this->isBlacklisted($column) && ! $this->hasFilterColumn($column)) ->each(function ($column) use ($keyword, $query) { if ($this->hasFilterColumn($column)) { $this->applyFilterColumn($query, $column, $keyword, 'or'); } else { $this->compileQuerySearch($query, $column, $keyword); } }); }); } /** * Perform multi-term search by splitting keyword into * individual words and searches for each of them. * * @param string $keyword */ protected function smartGlobalSearch($keyword): void { // Try scout search first & fall back to default search if disabled/failed if ($this->applyScoutSearch($keyword)) { return; } parent::smartGlobalSearch($keyword); } /** * Append debug parameters on output. */ protected function showDebugger(array $output): array { $query_log = $this->getConnection()->getQueryLog(); array_walk_recursive($query_log, function (&$item) { if (is_string($item) && extension_loaded('iconv')) { $item = iconv('iso-8859-1', 'utf-8', $item); } }); $output['queries'] = $query_log; $output['input'] = $this->request->all(); return $output; } /** * Attach custom with meta on response. */ protected function attachAppends(array $data): array { $appends = []; foreach ($this->appends as $key => $value) { if (is_callable($value)) { $appends[$key] = value($value($this->getFilteredQuery())); } else { $appends[$key] = $value; } } // Set flag to disable ordering $appends['disableOrdering'] = $this->disableUserOrdering; return array_merge($data, $appends); } /** * Get filtered, ordered and paginated query. */ public function getFilteredQuery(): QueryBuilder { $this->prepareQuery(); return $this->getQuery(); } /** * Ignore the selects in count query. * * @return $this */ public function ignoreSelectsInCountQuery(): static { $this->ignoreSelectInCountQuery = true; return $this; } /** * Perform sorting of columns. */ public function ordering(): void { // Skip if user ordering is disabled (e.g. scout search) if ($this->disableUserOrdering) { return; } parent::ordering(); } /** * Enable scout search and use provided model for searching. * $max_hits is the maximum number of hits to return from scout. * * @return $this * * @throws \Exception */ public function enableScoutSearch(string $model, int $max_hits = 1000): static { $scout_model = new $model; if (! class_exists($model) || ! ($scout_model instanceof Model)) { throw new \Exception("$model must be an Eloquent Model."); } if (! method_exists($scout_model, 'searchableAs') || ! method_exists($scout_model, 'getScoutKeyName')) { throw new \Exception("$model must use the Searchable trait."); } $this->scoutModel = $scout_model; $this->scoutMaxHits = $max_hits; $this->scoutIndex = $this->scoutModel->searchableAs(); $this->scoutKey = $this->scoutModel->getScoutKeyName(); return $this; } /** * Add dynamic filters to scout search. * * @return $this */ public function scoutFilter(callable $callback): static { $this->scoutFilterCallback = $callback; return $this; } /** * Apply scout search to query if enabled. */ protected function applyScoutSearch(string $search_keyword): bool { if ($this->scoutModel == null) { return false; } try { // Perform scout search $search_filters = ''; if (is_callable($this->scoutFilterCallback)) { $search_filters = ($this->scoutFilterCallback)($search_keyword); } $search_results = $this->performScoutSearch($search_keyword, $search_filters); // Apply scout search results to query $this->query->where(function ($query) use ($search_results) { $this->query->whereIn($this->scoutKey, $search_results); }); // Order by scout search results & disable user ordering (if db driver is supported) if (count($search_results) > 0 && $this->applyFixedOrderingToQuery($this->scoutKey, $search_results)) { // Disable user ordering because we already ordered by search relevancy $this->disableUserOrdering = true; } $this->scoutSearched = true; return true; } catch (\Exception) { // Scout search failed, fallback to default search return false; } } /** * Apply fixed ordering to query by a fixed set of values depending on database driver (used for scout search). * * Currently supported drivers: MySQL * * @return bool */ protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys) { $connection = $this->getConnection(); $driverName = $connection->getDriverName(); // Escape keyName and orderedKeys $keyName = $connection->getQueryGrammar()->wrap($keyName); $orderedKeys = collect($orderedKeys) ->map(fn ($value) => $connection->escape($value)); switch ($driverName) { case 'mariadb': case 'mysql': $this->query->orderByRaw("FIELD($keyName, ".$orderedKeys->implode(',').')'); return true; case 'pgsql': case 'oracle': $this->query->orderByRaw( 'CASE ' . $orderedKeys ->map(fn ($value, $index) => "WHEN $keyName=$value THEN $index") ->implode(' ') . ' END' ); return true; case 'sqlite': case 'sqlsrv': $this->query->orderByRaw( "CASE $keyName " . $orderedKeys ->map(fn ($value, $index) => "WHEN $value THEN $index") ->implode(' ') . ' END' ); return true; default: return false; } } /** * Perform a scout search with the configured engine and given parameters. Return matching model IDs. * * * @throws \Exception */ protected function performScoutSearch(string $searchKeyword, mixed $searchFilters = []): array { if (! class_exists(\Laravel\Scout\EngineManager::class)) { throw new \Exception('Laravel Scout is not installed.'); } $engine = app(\Laravel\Scout\EngineManager::class)->engine(); if ($engine instanceof \Laravel\Scout\Engines\MeilisearchEngine) { /** @var \Meilisearch\Client $engine */ $search_results = $engine ->index($this->scoutIndex) ->rawSearch($searchKeyword, [ 'limit' => $this->scoutMaxHits, 'attributesToRetrieve' => [$this->scoutKey], 'filter' => $searchFilters, ]); /** @var array<int, array<string, mixed>> $hits */ $hits = $search_results['hits'] ?? []; return collect($hits) ->pluck($this->scoutKey) ->all(); } elseif ($engine instanceof \Laravel\Scout\Engines\AlgoliaEngine) { /** @var \Algolia\AlgoliaSearch\SearchClient $engine */ $algolia = $engine->initIndex($this->scoutIndex); $search_results = $algolia->search($searchKeyword, [ 'offset' => 0, 'length' => $this->scoutMaxHits, 'attributesToRetrieve' => [$this->scoutKey], 'attributesToHighlight' => [], 'filters' => $searchFilters, ]); /** @var array<int, array<string, mixed>> $hits */ $hits = $search_results['hits'] ?? []; return collect($hits) ->pluck($this->scoutKey) ->all(); } else { throw new \Exception('Unsupported Scout Engine. Currently supported: Meilisearch, Algolia'); } } } src/Processors/DataProcessor.php 0000644 00000015465 15060250030 0012761 0 ustar 00 <?php namespace Yajra\DataTables\Processors; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Support\Arr; use Yajra\DataTables\Contracts\Formatter; use Yajra\DataTables\Utilities\Helper; class DataProcessor { protected array $output = []; /** * @var array<array-key, array{name: string, content: mixed}> */ protected array $appendColumns = []; /** * @var array<array-key, array{name: string, content: mixed}> */ protected array $editColumns = []; protected array $rawColumns = []; /** * @var array|string[] */ protected array $exceptions = ['DT_RowId', 'DT_RowClass', 'DT_RowData', 'DT_RowAttr']; protected array $onlyColumns = []; protected array $makeHidden = []; protected array $makeVisible = []; protected array $excessColumns = []; /** * @var string|array */ protected mixed $escapeColumns = []; protected bool $includeIndex = false; protected bool $ignoreGetters = false; public function __construct(protected iterable $results, array $columnDef, protected array $templates, protected int $start = 0) { $this->appendColumns = $columnDef['append'] ?? []; $this->editColumns = $columnDef['edit'] ?? []; $this->excessColumns = $columnDef['excess'] ?? []; $this->onlyColumns = $columnDef['only'] ?? []; $this->escapeColumns = $columnDef['escape'] ?? []; $this->includeIndex = $columnDef['index'] ?? false; $this->rawColumns = $columnDef['raw'] ?? []; $this->makeHidden = $columnDef['hidden'] ?? []; $this->makeVisible = $columnDef['visible'] ?? []; $this->ignoreGetters = $columnDef['ignore_getters'] ?? false; } /** * Process data to output on browser. * * @param bool $object */ public function process($object = false): array { $this->output = []; $indexColumn = config('datatables.index_column', 'DT_RowIndex'); foreach ($this->results as $row) { $data = Helper::convertToArray($row, ['hidden' => $this->makeHidden, 'visible' => $this->makeVisible, 'ignore_getters' => $this->ignoreGetters]); $value = $this->addColumns($data, $row); $value = $this->editColumns($value, $row); $value = $this->setupRowVariables($value, $row); $value = $this->selectOnlyNeededColumns($value); $value = $this->removeExcessColumns($value); if ($this->includeIndex) { $value[$indexColumn] = ++$this->start; } $this->output[] = $object ? $value : $this->flatten($value); } return $this->escapeColumns($this->output); } /** * Process add columns. * * @param array|object|\Illuminate\Database\Eloquent\Model $row */ protected function addColumns(array $data, $row): array { foreach ($this->appendColumns as $value) { $content = $value['content']; if ($content instanceof Formatter) { $column = str_replace('_formatted', '', $value['name']); $value['content'] = $content->format($data[$column], $row); if (isset($data[$column])) { $value['content'] = $content->format($data[$column], $row); } } else { $value['content'] = Helper::compileContent($content, $data, $row); } $data = Helper::includeInArray($value, $data); } return $data; } /** * Process edit columns. */ protected function editColumns(array $data, object|array $row): array { foreach ($this->editColumns as $value) { $value['content'] = Helper::compileContent($value['content'], $data, $row); Arr::set($data, $value['name'], $value['content']); } return $data; } /** * Setup additional DT row variables. */ protected function setupRowVariables(array $data, object|array $row): array { $processor = new RowProcessor($data, $row); return $processor ->rowValue('DT_RowId', $this->templates['DT_RowId']) ->rowValue('DT_RowClass', $this->templates['DT_RowClass']) ->rowData('DT_RowData', $this->templates['DT_RowData']) ->rowData('DT_RowAttr', $this->templates['DT_RowAttr']) ->getData(); } /** * Get only needed columns. */ protected function selectOnlyNeededColumns(array $data): array { if (empty($this->onlyColumns)) { return $data; } else { $results = []; foreach ($this->onlyColumns as $onlyColumn) { Arr::set($results, $onlyColumn, Arr::get($data, $onlyColumn)); } foreach ($this->exceptions as $exception) { if ($column = Arr::get($data, $exception)) { Arr::set($results, $exception, $column); } } return $results; } } /** * Remove declared hidden columns. */ protected function removeExcessColumns(array $data): array { foreach ($this->excessColumns as $value) { Arr::forget($data, $value); } return $data; } /** * Flatten array with exceptions. */ public function flatten(array $array): array { $return = []; foreach ($array as $key => $value) { if (in_array($key, $this->exceptions)) { $return[$key] = $value; } else { $return[] = $value; } } return $return; } /** * Escape column values as declared. */ protected function escapeColumns(array $output): array { return array_map(function ($row) { if ($this->escapeColumns == '*') { $row = $this->escapeRow($row); } elseif (is_array($this->escapeColumns)) { $columns = array_diff($this->escapeColumns, $this->rawColumns); foreach ($columns as $key) { /** @var string $content */ $content = Arr::get($row, $key); Arr::set($row, $key, e($content)); } } return $row; }, $output); } /** * Escape all string or Htmlable values of row. */ protected function escapeRow(array $row): array { $arrayDot = array_filter(Arr::dot($row)); foreach ($arrayDot as $key => $value) { if (! in_array($key, $this->rawColumns)) { $arrayDot[$key] = (is_string($value) || $value instanceof Htmlable) ? e($value) : $value; } } foreach ($arrayDot as $key => $value) { Arr::set($row, $key, $value); } return $row; } } src/Processors/RowProcessor.php 0000644 00000002752 15060250030 0012652 0 ustar 00 <?php namespace Yajra\DataTables\Processors; use Illuminate\Support\Arr; use Yajra\DataTables\Utilities\Helper; class RowProcessor { /** * @param array|object $row */ public function __construct(protected array $data, protected $row) { } /** * Process DT RowId and Class value. * * @param string $attribute * @param string|callable $template * @return $this * * @throws \ReflectionException */ public function rowValue($attribute, $template) { if (! empty($template)) { if (! is_callable($template) && Arr::get($this->data, $template)) { $this->data[$attribute] = Arr::get($this->data, $template); } else { $this->data[$attribute] = Helper::compileContent($template, $this->data, $this->row); } } return $this; } /** * Process DT Row Data and Attr. * * @param string $attribute * @return $this * * @throws \ReflectionException */ public function rowData($attribute, array $template) { if (count($template)) { $this->data[$attribute] = []; foreach ($template as $key => $value) { $this->data[$attribute][$key] = Helper::compileContent($value, $this->data, $this->row); } } return $this; } /** * @return array */ public function getData() { return $this->data; } } src/DataTables.php 0000644 00000010562 15060250030 0010043 0 ustar 00 <?php namespace Yajra\DataTables; use Illuminate\Contracts\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Contracts\Database\Query\Builder as QueryBuilder; use Illuminate\Support\Traits\Macroable; use Yajra\DataTables\Exceptions\Exception; use Yajra\DataTables\Utilities\Config; use Yajra\DataTables\Utilities\Request; class DataTables { use Macroable; /** * DataTables request object. */ protected Utilities\Request $request; /** * Make a DataTable instance from source. * Alias of make for backward compatibility. * * @param object $source * @return DataTableAbstract * * @throws \Exception */ public static function of($source) { return self::make($source); } /** * Make a DataTable instance from source. * * @param object $source * @return DataTableAbstract * * @throws \Yajra\DataTables\Exceptions\Exception */ public static function make($source) { $engines = (array) config('datatables.engines'); $builders = (array) config('datatables.builders'); $args = func_get_args(); foreach ($builders as $class => $engine) { if ($source instanceof $class) { $callback = [$engines[$engine], 'create']; if (is_callable($callback)) { /** @var \Yajra\DataTables\DataTableAbstract $instance */ $instance = call_user_func_array($callback, $args); return $instance; } } } foreach ($engines as $engine) { $canCreate = [$engine, 'canCreate']; if (is_callable($canCreate) && call_user_func_array($canCreate, $args)) { $create = [$engine, 'create']; if (is_callable($create)) { /** @var \Yajra\DataTables\DataTableAbstract $instance */ $instance = call_user_func_array($create, $args); return $instance; } } } throw new Exception('No available engine for '.$source::class); } /** * Get request object. */ public function getRequest(): Request { return app('datatables.request'); } /** * Get config instance. */ public function getConfig(): Config { return app('datatables.config'); } /** * DataTables using query builder. * * @throws \Yajra\DataTables\Exceptions\Exception */ public function query(QueryBuilder $builder): QueryDataTable { /** @var string $dataTable */ $dataTable = config('datatables.engines.query'); $this->validateDataTable($dataTable, QueryDataTable::class); return $dataTable::create($builder); } /** * DataTables using Eloquent Builder. * * @throws \Yajra\DataTables\Exceptions\Exception */ public function eloquent(EloquentBuilder $builder): EloquentDataTable { /** @var string $dataTable */ $dataTable = config('datatables.engines.eloquent'); $this->validateDataTable($dataTable, EloquentDataTable::class); return $dataTable::create($builder); } /** * DataTables using Collection. * * @param \Illuminate\Support\Collection<array-key, array>|array $collection * * @throws \Yajra\DataTables\Exceptions\Exception */ public function collection($collection): CollectionDataTable { /** @var string $dataTable */ $dataTable = config('datatables.engines.collection'); $this->validateDataTable($dataTable, CollectionDataTable::class); return $dataTable::create($collection); } /** * DataTables using Collection. * * @param \Illuminate\Http\Resources\Json\AnonymousResourceCollection<array-key, array>|array $resource * @return ApiResourceDataTable|DataTableAbstract */ public function resource($resource) { return ApiResourceDataTable::create($resource); } /** * @throws \Yajra\DataTables\Exceptions\Exception */ public function validateDataTable(string $engine, string $parent): void { if (! ($engine == $parent || is_subclass_of($engine, $parent))) { throw new Exception("The given datatable engine `$engine` is not compatible with `$parent`."); } } } src/helper.php 0000644 00000001356 15060250030 0007317 0 ustar 00 <?php if (! function_exists('datatables')) { /** * Helper to make a new DataTable instance from source. * Or return the factory if source is not set. * * @param \Illuminate\Contracts\Database\Query\Builder|\Illuminate\Contracts\Database\Eloquent\Builder|\Illuminate\Support\Collection|array|null $source * @return \Yajra\DataTables\DataTables|\Yajra\DataTables\DataTableAbstract * * @throws \Yajra\DataTables\Exceptions\Exception */ function datatables($source = null) { /** @var Yajra\DataTables\DataTables $dataTable */ $dataTable = app('datatables'); if (is_null($source)) { return $dataTable; } return $dataTable->make($source); } } src/Utilities/Request.php 0000644 00000013333 15060250030 0011441 0 ustar 00 <?php namespace Yajra\DataTables\Utilities; use Illuminate\Http\Request as BaseRequest; /** * @mixin \Illuminate\Http\Request */ class Request { protected BaseRequest $request; /** * Request constructor. */ public function __construct() { $this->request = app('request'); } /** * Proxy non-existing method calls to base request class. * * @param string $name * @param array $arguments * @return mixed */ public function __call($name, $arguments) { $callback = [$this->request, $name]; if (is_callable($callback)) { return call_user_func_array($callback, $arguments); } } /** * Get attributes from request instance. * * @param string $name * @return mixed */ public function __get($name) { return $this->request->__get($name); } /** * Get all columns request input. */ public function columns(): array { return (array) $this->request->input('columns'); } /** * Check if DataTables is searchable. */ public function isSearchable(): bool { return $this->request->input('search.value') != ''; } /** * Check if DataTables must uses regular expressions. */ public function isRegex(int $index): bool { return $this->request->input("columns.$index.search.regex") === 'true'; } /** * Get orderable columns. */ public function orderableColumns(): array { if (! $this->isOrderable()) { return []; } $orderable = []; for ($i = 0, $c = count((array) $this->request->input('order')); $i < $c; $i++) { /** @var int $order_col */ $order_col = $this->request->input("order.$i.column"); /** @var string $direction */ $direction = $this->request->input("order.$i.dir"); $order_dir = strtolower($direction) === 'asc' ? 'asc' : 'desc'; if ($this->isColumnOrderable($order_col)) { $orderable[] = ['column' => $order_col, 'direction' => $order_dir]; } } return $orderable; } /** * Check if DataTables ordering is enabled. */ public function isOrderable(): bool { return $this->request->input('order') && count((array) $this->request->input('order')) > 0; } /** * Check if a column is orderable. */ public function isColumnOrderable(int $index): bool { return $this->request->input("columns.$index.orderable", 'true') == 'true'; } /** * Get searchable column indexes. * * @return array */ public function searchableColumnIndex() { $searchable = []; $columns = (array) $this->request->input('columns'); for ($i = 0, $c = count($columns); $i < $c; $i++) { if ($this->isColumnSearchable($i, false)) { $searchable[] = $i; } } return $searchable; } /** * Check if a column is searchable. */ public function isColumnSearchable(int $i, bool $column_search = true): bool { if ($column_search) { return ( $this->request->input("columns.$i.searchable", 'true') === 'true' || $this->request->input("columns.$i.searchable", 'true') === true ) && $this->columnKeyword($i) != ''; } return $this->request->input("columns.$i.searchable", 'true') === 'true' || $this->request->input("columns.$i.searchable", 'true') === true; } /** * Get column's search value. */ public function columnKeyword(int $index): string { /** @var string $keyword */ $keyword = $this->request->input("columns.$index.search.value") ?? ''; return $this->prepareKeyword($keyword); } /** * Prepare keyword string value. */ protected function prepareKeyword(float|array|int|string $keyword): string { if (is_array($keyword)) { return implode(' ', $keyword); } return (string) $keyword; } /** * Get global search keyword. */ public function keyword(): string { /** @var string $keyword */ $keyword = $this->request->input('search.value') ?? ''; return $this->prepareKeyword($keyword); } /** * Get column name by index. */ public function columnName(int $i): ?string { /** @var string[] $column */ $column = $this->request->input("columns.$i"); return (isset($column['name']) && $column['name'] != '') ? $column['name'] : $column['data']; } /** * Check if DataTables allow pagination. */ public function isPaginationable(): bool { return ! is_null($this->request->input('start')) && ! is_null($this->request->input('length')) && $this->request->input('length') != -1; } public function getBaseRequest(): BaseRequest { return $this->request; } /** * Get starting record value. */ public function start(): int { $start = $this->request->input('start', 0); return is_numeric($start) ? intval($start) : 0; } /** * Get per page length. */ public function length(): int { $length = $this->request->input('length', 10); return is_numeric($length) ? intval($length) : 10; } /** * Get draw request. */ public function draw(): int { $draw = $this->request->input('draw', 0); return is_numeric($draw) ? intval($draw) : 0; } } src/Utilities/Helper.php 0000644 00000025100 15060250030 0011223 0 ustar 00 <?php namespace Yajra\DataTables\Utilities; use Closure; use DateTime; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Support\Arr; use Illuminate\Support\Str; use ReflectionFunction; use ReflectionMethod; class Helper { /** * Places item of extra columns into results by care of their order. */ public static function includeInArray(array $item, array $array): array { if (self::isItemOrderInvalid($item, $array)) { return array_merge($array, [$item['name'] => $item['content']]); } $count = 0; $last = $array; $first = []; foreach ($array as $key => $value) { if ($count == $item['order']) { continue; } unset($last[$key]); $first[$key] = $value; $count++; } return array_merge($first, [$item['name'] => $item['content']], $last); } /** * Check if item order is valid. */ protected static function isItemOrderInvalid(array $item, array $array): bool { return $item['order'] === false || $item['order'] >= count($array); } /** * Gets the parameter of a callable thing (from is_callable) and returns it's arguments using reflection. * * @param callable $callable * @return \ReflectionParameter[] * * @throws \ReflectionException * @throws \InvalidArgumentException */ private static function reflectCallableParameters($callable) { /* loosely after https://github.com/technically-php/callable-reflection/blob/main/src/CallableReflection.php#L72-L86. Licence is compatible, both project use MIT */ if ($callable instanceof Closure) { $reflection = new ReflectionFunction($callable); } elseif (is_string($callable) && function_exists($callable)) { $reflection = new ReflectionFunction($callable); } elseif (is_string($callable) && str_contains($callable, '::')) { $reflection = new ReflectionMethod($callable); } elseif (is_object($callable) && method_exists($callable, '__invoke')) { $reflection = new ReflectionMethod($callable, '__invoke'); } else { throw new \InvalidArgumentException('argument is not callable or the code is wrong'); } return $reflection->getParameters(); } /** * Determines if content is callable or blade string, processes and returns. * * @param mixed $content Pre-processed content * @param array $data data to use with blade template * @param array|object $param parameter to call with callable * @return mixed * * @throws \ReflectionException */ public static function compileContent(mixed $content, array $data, array|object $param) { if (is_string($content)) { return static::compileBlade($content, static::getMixedValue($data, $param)); } if (is_callable($content)) { $arguments = self::reflectCallableParameters($content); if (count($arguments) > 0) { return app()->call($content, [$arguments[0]->name => $param]); } return $content($param); } if (is_array($content)) { [$view, $viewData] = $content; return static::compileBlade($view, static::getMixedValue($data, $param) + $viewData); } return $content; } /** * Parses and compiles strings by using Blade Template System. * * * @throws \Throwable */ public static function compileBlade(string $str, array $data = []): false|string { if (view()->exists($str)) { /** @var view-string $str */ return view($str, $data)->render(); } ob_start() && extract($data, EXTR_SKIP); eval('?>'.app('blade.compiler')->compileString($str)); $str = ob_get_contents(); ob_end_clean(); return $str; } /** * Get a mixed value of custom data and the parameters. */ public static function getMixedValue(array $data, array|object $param): array { $casted = self::castToArray($param); $data['model'] = $param; foreach ($data as $key => $value) { if (isset($casted[$key])) { $data[$key] = $casted[$key]; } } return $data; } /** * Cast the parameter into an array. */ public static function castToArray(array|object $param): array { if ($param instanceof Arrayable) { return $param->toArray(); } return (array) $param; } /** * Get equivalent or method of query builder. */ public static function getOrMethod(string $method): string { if (! Str::contains(Str::lower($method), 'or')) { return 'or'.ucfirst($method); } return $method; } /** * Converts array object values to associative array. */ public static function convertToArray(mixed $row, array $filters = []): array { if (Arr::get($filters, 'ignore_getters') && is_object($row) && method_exists($row, 'getAttributes')) { $data = $row->getAttributes(); if (method_exists($row, 'getRelations')) { foreach ($row->getRelations() as $relationName => $relation) { if (is_iterable($relation)) { foreach ($relation as $relationItem) { $data[$relationName][] = self::convertToArray($relationItem, ['ignore_getters' => true]); } } else { $data[$relationName] = self::convertToArray($relation, ['ignore_getters' => true]); } } } return $data; } $row = is_object($row) && method_exists($row, 'makeHidden') ? $row->makeHidden(Arr::get($filters, 'hidden', [])) : $row; $row = is_object($row) && method_exists($row, 'makeVisible') ? $row->makeVisible(Arr::get($filters, 'visible', [])) : $row; $data = $row instanceof Arrayable ? $row->toArray() : (array) $row; foreach ($data as &$value) { if (is_object($value) || is_array($value)) { $value = self::convertToArray($value); } unset($value); } return $data; } public static function transform(array $data): array { return array_map(fn ($row) => self::transformRow($row), $data); } /** * Transform row data into an array. * * @param array $row */ protected static function transformRow($row): array { foreach ($row as $key => $value) { if ($value instanceof DateTime) { $row[$key] = $value->format('Y-m-d H:i:s'); } else { if (is_object($value) && method_exists($value, '__toString')) { $row[$key] = $value->__toString(); } else { $row[$key] = $value; } } } return $row; } /** * Build parameters depending on # of arguments passed. */ public static function buildParameters(array $args): array { $parameters = []; if (count($args) > 2) { $parameters[] = $args[0]; foreach ($args[1] as $param) { $parameters[] = $param; } } else { foreach ($args[0] as $param) { $parameters[] = $param; } } return $parameters; } /** * Replace all pattern occurrences with keyword. */ public static function replacePatternWithKeyword(array $subject, string $keyword, string $pattern = '$1'): array { $parameters = []; foreach ($subject as $param) { if (is_array($param)) { $parameters[] = self::replacePatternWithKeyword($param, $keyword, $pattern); } else { $parameters[] = str_replace($pattern, $keyword, (string) $param); } } return $parameters; } /** * Get column name from string. */ public static function extractColumnName(string $str, bool $wantsAlias): string { $matches = explode(' as ', Str::lower($str)); if (count($matches) > 1) { if ($wantsAlias) { return array_pop($matches); } return array_shift($matches); } elseif (strpos($str, '.')) { $array = explode('.', $str); return array_pop($array); } return $str; } /** * Adds % wildcards to the given string. */ public static function wildcardLikeString(string $str, bool $lowercase = true): string { return static::wildcardString($str, '%', $lowercase); } /** * Adds wildcards to the given string. */ public static function wildcardString(string $str, string $wildcard, bool $lowercase = true): string { $wild = $wildcard; $chars = (array) preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY); if (count($chars) > 0) { foreach ($chars as $char) { $wild .= $char.$wildcard; } } if ($lowercase) { $wild = Str::lower($wild); } return $wild; } public static function toJsonScript(array $parameters, int $options = 0): string { $values = []; $replacements = []; foreach (Arr::dot($parameters) as $key => $value) { if (self::isJavascript($value, $key)) { $values[] = trim((string) $value); Arr::set($parameters, $key, '%'.$key.'%'); $replacements[] = '"%'.$key.'%"'; } } $new = []; foreach ($parameters as $key => $value) { Arr::set($new, $key, $value); } $json = (string) json_encode($new, $options); return str_replace($replacements, $values, $json); } public static function isJavascript(string|array|object|null $value, string $key): bool { if (empty($value) || is_array($value) || is_object($value)) { return false; } /** @var array $callbacks */ $callbacks = config('datatables.callback', ['$', '$.', 'function']); if (Str::startsWith($key, 'language.')) { return false; } return Str::startsWith(trim($value), $callbacks) || Str::contains($key, ['editor', 'minDate', 'maxDate']); } } src/Utilities/Config.php 0000644 00000004277 15060250030 0011225 0 ustar 00 <?php namespace Yajra\DataTables\Utilities; use Illuminate\Contracts\Config\Repository; class Config { /** * Config constructor. */ public function __construct(private readonly Repository $repository) { } /** * Check if config uses wild card search. */ public function isWildcard(): bool { return (bool) $this->repository->get('datatables.search.use_wildcards', false); } /** * Check if config uses smart search. */ public function isSmartSearch(): bool { return (bool) $this->repository->get('datatables.search.smart', true); } /** * Check if config uses case-insensitive search. */ public function isCaseInsensitive(): bool { return (bool) $this->repository->get('datatables.search.case_insensitive', false); } /** * Check if app is in debug mode. */ public function isDebugging(): bool { return (bool) $this->repository->get('app.debug', false); } /** * Get the specified configuration value. * * @param string $key * @return mixed */ public function get($key, mixed $default = null) { return $this->repository->get($key, $default); } /** * Set a given configuration value. * * @param array|string $key * @return void */ public function set($key, mixed $value = null) { $this->repository->set($key, $value); } /** * Check if dataTable config uses multi-term searching. */ public function isMultiTerm(): bool { return (bool) $this->repository->get('datatables.search.multi_term', true); } /** * Check if dataTable config uses starts_with searching. */ public function isStartsWithSearch(): bool { return (bool) $this->repository->get('datatables.search.starts_with', false); } public function jsonOptions(): int { /** @var int $options */ $options = $this->repository->get('datatables.json.options', 0); return $options; } public function jsonHeaders(): array { return (array) $this->repository->get('datatables.json.header', []); } } src/DataTableAbstract.php 0000644 00000054515 15060250030 0011352 0 ustar 00 <?php namespace Yajra\DataTables; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Http\JsonResponse; use Illuminate\Support\Arr; use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; use Psr\Log\LoggerInterface; use Yajra\DataTables\Contracts\DataTable; use Yajra\DataTables\Contracts\Formatter; use Yajra\DataTables\Processors\DataProcessor; use Yajra\DataTables\Utilities\Helper; /** * @method static setTransformer($transformer) * @method static setSerializer($transformer) * * @property-read mixed $transformer * @property-read mixed $serializer * * @see https://github.com/yajra/laravel-datatables-fractal for transformer related methods. */ abstract class DataTableAbstract implements DataTable { use Macroable; /** * DataTables Request object. */ public Utilities\Request $request; protected ?LoggerInterface $logger = null; /** * Array of result columns/fields. */ protected ?array $columns = []; /** * DT columns definitions container (add/edit/remove/filter/order/escape). */ protected array $columnDef = [ 'index' => false, 'ignore_getters' => false, 'append' => [], 'edit' => [], 'filter' => [], 'order' => [], 'only' => null, 'hidden' => [], 'visible' => [], ]; /** * Extra/Added columns. */ protected array $extraColumns = []; /** * Total records. */ protected ?int $totalRecords = null; /** * Total filtered records. */ protected ?int $filteredRecords = null; /** * Auto-filter flag. */ protected bool $autoFilter = true; /** * Callback to override global search. * * @var callable */ protected $filterCallback = null; /** * DT row templates container. */ protected array $templates = [ 'DT_RowId' => '', 'DT_RowClass' => '', 'DT_RowData' => [], 'DT_RowAttr' => [], ]; /** * Custom ordering callback. * * @var callable|null */ protected $orderCallback = null; /** * Skip pagination as needed. */ protected bool $skipPaging = false; /** * Array of data to append on json response. */ protected array $appends = []; protected Utilities\Config $config; protected mixed $serializer; protected array $searchPanes = []; protected mixed $transformer; protected bool $editOnlySelectedColumns = false; /** * Can the DataTable engine be created with these parameters. * * @return bool */ public static function canCreate(mixed $source) { return false; } /** * Factory method, create and return an instance for the DataTable engine. * * @return static */ public static function create(mixed $source) { return new static($source); } /** * @param string|array $columns * @param string|callable|\Yajra\DataTables\Contracts\Formatter $formatter * @return $this */ public function formatColumn($columns, $formatter): static { if (is_string($formatter) && class_exists($formatter)) { $formatter = app($formatter); } if ($formatter instanceof Formatter) { foreach ((array) $columns as $column) { $this->addColumn($column.'_formatted', $formatter); } return $this; } if (is_callable($formatter)) { foreach ((array) $columns as $column) { $this->addColumn( $column.'_formatted', fn ($row) => $formatter(data_get($row, $column), $row) ); } return $this; } foreach ((array) $columns as $column) { $this->addColumn( $column.'_formatted', fn ($row) => data_get($row, $column) ); } return $this; } /** * Add column in collection. * * @param string $name * @param string|callable|Formatter $content * @param bool|int $order * @return $this */ public function addColumn($name, $content, $order = false): static { $this->extraColumns[] = $name; $this->columnDef['append'][] = ['name' => $name, 'content' => $content, 'order' => $order]; return $this; } /** * Add DT row index column on response. * * @return $this */ public function addIndexColumn(): static { $this->columnDef['index'] = true; return $this; } /** * Prevent the getters Mutators to be applied when converting a collection * of the Models into the final JSON. * * @return $this */ public function ignoreGetters(): static { $this->columnDef['ignore_getters'] = true; return $this; } /** * Edit column's content. * * @param string $name * @param string|callable $content * @return $this */ public function editColumn($name, $content): static { if ($this->editOnlySelectedColumns) { if (! count($this->request->columns()) || in_array($name, Arr::pluck($this->request->columns(), 'name'))) { $this->columnDef['edit'][] = ['name' => $name, 'content' => $content]; } } else { $this->columnDef['edit'][] = ['name' => $name, 'content' => $content]; } return $this; } /** * Remove column from collection. * * @return $this */ public function removeColumn(): static { $names = func_get_args(); $this->columnDef['excess'] = array_merge($this->getColumnsDefinition()['excess'], $names); return $this; } /** * Get columns definition. */ protected function getColumnsDefinition(): array { $config = (array) $this->config->get('datatables.columns'); $allowed = ['excess', 'escape', 'raw', 'blacklist', 'whitelist']; return array_replace_recursive(Arr::only($config, $allowed), $this->columnDef); } /** * Get only selected columns in response. * * @return $this */ public function only(array $columns = []): static { $this->columnDef['only'] = $columns; return $this; } /** * Declare columns to escape values. * * @param string|array $columns * @return $this */ public function escapeColumns($columns = '*'): static { $this->columnDef['escape'] = $columns; return $this; } /** * Add a makeHidden() to the row object. * * @return $this */ public function makeHidden(array $attributes = []): static { $hidden = (array) Arr::get($this->columnDef, 'hidden', []); $this->columnDef['hidden'] = array_merge_recursive($hidden, $attributes); return $this; } /** * Add a makeVisible() to the row object. * * @return $this */ public function makeVisible(array $attributes = []): static { $visible = (array) Arr::get($this->columnDef, 'visible', []); $this->columnDef['visible'] = array_merge_recursive($visible, $attributes); return $this; } /** * Set columns that should not be escaped. * Optionally merge the defaults from config. * * @param bool $merge * @return $this */ public function rawColumns(array $columns, $merge = false): static { if ($merge) { /** @var array[] $config */ $config = $this->config->get('datatables.columns'); $this->columnDef['raw'] = array_merge($config['raw'], $columns); } else { $this->columnDef['raw'] = $columns; } return $this; } /** * Sets DT_RowClass template. * result: <tr class="output_from_your_template">. * * @param string|callable $content * @return $this */ public function setRowClass($content): static { $this->templates['DT_RowClass'] = $content; return $this; } /** * Sets DT_RowId template. * result: <tr id="output_from_your_template">. * * @param string|callable $content * @return $this */ public function setRowId($content): static { $this->templates['DT_RowId'] = $content; return $this; } /** * Set DT_RowData templates. * * @return $this */ public function setRowData(array $data): static { $this->templates['DT_RowData'] = $data; return $this; } /** * Add DT_RowData template. * * @param string $key * @param string|callable $value * @return $this */ public function addRowData($key, $value): static { $this->templates['DT_RowData'][$key] = $value; return $this; } /** * Set DT_RowAttr templates. * result: <tr attr1="attr1" attr2="attr2">. * * @return $this */ public function setRowAttr(array $data): static { $this->templates['DT_RowAttr'] = $data; return $this; } /** * Add DT_RowAttr template. * * @param string $key * @param string|callable $value * @return $this */ public function addRowAttr($key, $value): static { $this->templates['DT_RowAttr'][$key] = $value; return $this; } /** * Append data on json response. * * @return $this */ public function with(mixed $key, mixed $value = ''): static { if (is_array($key)) { $this->appends = $key; } else { $this->appends[$key] = value($value); } return $this; } /** * Add with query callback value on response. * * @return $this */ public function withQuery(string $key, callable $value): static { $this->appends[$key] = $value; return $this; } /** * Override default ordering method with a closure callback. * * @return $this */ public function order(callable $closure): static { $this->orderCallback = $closure; return $this; } /** * Update list of columns that is not allowed for search/sort. * * @return $this */ public function blacklist(array $blacklist): static { $this->columnDef['blacklist'] = $blacklist; return $this; } /** * Update list of columns that is allowed for search/sort. * * @return $this */ public function whitelist(array|string $whitelist = '*'): static { $this->columnDef['whitelist'] = $whitelist; return $this; } /** * Set smart search config at runtime. * * @return $this */ public function smart(bool $state = true): static { $this->config->set('datatables.search.smart', $state); return $this; } /** * Set starts_with search config at runtime. * * @return $this */ public function startsWithSearch(bool $state = true): static { $this->config->set('datatables.search.starts_with', $state); return $this; } /** * Set multi_term search config at runtime. * * @return $this */ public function setMultiTerm(bool $multiTerm = true): static { $this->config->set('datatables.search.multi_term', $multiTerm); return $this; } /** * Set total records manually. * * @return $this */ public function setTotalRecords(int $total): static { $this->totalRecords = $total; return $this; } /** * Skip total records and set the recordsTotal equals to recordsFiltered. * This will improve the performance by skipping the total count query. * * @return $this * * @deprecated Just use setTotalRecords instead. */ public function skipTotalRecords(): static { $this->totalRecords = 0; return $this; } /** * Set filtered records manually. * * @return $this */ public function setFilteredRecords(int $total): static { $this->filteredRecords = $total; return $this; } /** * Skip pagination as needed. * * @return $this */ public function skipPaging(): static { $this->skipPaging = true; return $this; } /** * Skip auto filtering as needed. * * @return $this */ public function skipAutoFilter(): static { $this->autoFilter = false; return $this; } /** * Push a new column name to blacklist. * * @param string $column * @return $this */ public function pushToBlacklist($column): static { if (! $this->isBlacklisted($column)) { $this->columnDef['blacklist'][] = $column; } return $this; } /** * Check if column is blacklisted. * * @param string $column */ protected function isBlacklisted($column): bool { $colDef = $this->getColumnsDefinition(); if (in_array($column, $colDef['blacklist'])) { return true; } if ($colDef['whitelist'] === '*' || in_array($column, $colDef['whitelist'])) { return false; } return true; } /** * Perform sorting of columns. */ public function ordering(): void { if ($this->orderCallback) { call_user_func_array($this->orderCallback, $this->resolveCallbackParameter()); } else { $this->defaultOrdering(); } } /** * Resolve callback parameter instance. * * @return array<int|string, mixed> */ abstract protected function resolveCallbackParameter(); /** * Perform default query orderBy clause. */ abstract protected function defaultOrdering(): void; /** * Set auto filter off and run your own filter. * Overrides global search. * * @return $this */ public function filter(callable $callback, bool $globalSearch = false): self { $this->autoFilter = $globalSearch; $this->filterCallback = $callback; return $this; } /** * Convert the object to its JSON representation. * * @param int $options * @return \Illuminate\Http\JsonResponse */ public function toJson($options = 0) { if ($options) { $this->config->set('datatables.json.options', $options); } return $this->make(); } /** * Add a search pane options on response. * * @param string $column * @return $this */ public function searchPane($column, mixed $options, ?callable $builder = null): static { $options = value($options); if ($options instanceof Arrayable) { $options = $options->toArray(); } $this->searchPanes[$column]['options'] = $options; $this->searchPanes[$column]['builder'] = $builder; return $this; } /** * Convert instance to array. */ public function toArray(): array { return (array) $this->make()->getData(true); } /** * Count total items. */ public function totalCount(): int { return $this->totalRecords ??= $this->count(); } public function editOnlySelectedColumns(): static { $this->editOnlySelectedColumns = true; return $this; } /** * Perform necessary filters. */ protected function filterRecords(): void { if ($this->autoFilter && $this->request->isSearchable()) { $this->filtering(); } if (is_callable($this->filterCallback)) { call_user_func_array($this->filterCallback, $this->resolveCallbackParameter()); } $this->columnSearch(); $this->searchPanesSearch(); $this->filteredCount(); } /** * Perform global search. */ public function filtering(): void { $keyword = $this->request->keyword(); if ($this->config->isMultiTerm()) { $this->smartGlobalSearch($keyword); return; } $this->globalSearch($keyword); } /** * Perform multi-term search by splitting keyword into * individual words and searches for each of them. * * @param string $keyword */ protected function smartGlobalSearch($keyword): void { collect(explode(' ', $keyword)) ->reject(fn ($keyword) => trim((string) $keyword) === '') ->each(function ($keyword) { $this->globalSearch($keyword); }); } /** * Perform global search for the given keyword. */ abstract protected function globalSearch(string $keyword): void; /** * Perform search using search pane values. */ protected function searchPanesSearch(): void { // Add support for search pane. } /** * Count filtered items. */ protected function filteredCount(): int { return $this->filteredRecords ??= $this->count(); } /** * Apply pagination. */ protected function paginate(): void { if ($this->request->isPaginationable() && ! $this->skipPaging) { $this->paging(); } } /** * Transform output. * * @param iterable $results * @param array $processed */ protected function transform($results, $processed): array { if (isset($this->transformer) && class_exists('Yajra\\DataTables\\Transformers\\FractalTransformer')) { return app('datatables.transformer')->transform( $results, $this->transformer, $this->serializer ?? null ); } return Helper::transform($processed); } /** * Get processed data. * * @param iterable $results * @param bool $object * * @throws \Exception */ protected function processResults($results, $object = false): array { $processor = new DataProcessor( $results, $this->getColumnsDefinition(), $this->templates, $this->request->start() ); return $processor->process($object); } /** * Render json response. */ protected function render(array $data): JsonResponse { $output = $this->attachAppends([ 'draw' => $this->request->draw(), 'recordsTotal' => $this->totalRecords, 'recordsFiltered' => $this->filteredRecords ?? 0, 'data' => $data, ]); if ($this->config->isDebugging()) { $output = $this->showDebugger($output); } foreach ($this->searchPanes as $column => $searchPane) { $output['searchPanes']['options'][$column] = $searchPane['options']; } return new JsonResponse( $output, 200, $this->config->jsonHeaders(), $this->config->jsonOptions() ); } /** * Attach custom with meta on response. */ protected function attachAppends(array $data): array { return array_merge($data, $this->appends); } /** * Append debug parameters on output. */ protected function showDebugger(array $output): array { $output['input'] = $this->request->all(); return $output; } /** * Return an error json response. * * @throws \Yajra\DataTables\Exceptions\Exception|\Exception */ protected function errorResponse(\Exception $exception): JsonResponse { /** @var string $error */ $error = $this->config->get('datatables.error'); $debug = $this->config->get('app.debug'); if ($error === 'throw' || (! $error && ! $debug)) { throw $exception; } $this->getLogger()->error($exception); return new JsonResponse([ 'draw' => $this->request->draw(), 'recordsTotal' => $this->totalRecords, 'recordsFiltered' => 0, 'data' => [], 'error' => $error ? __($error) : 'Exception Message:'.PHP_EOL.PHP_EOL.$exception->getMessage(), ]); } /** * Get monolog/logger instance. * * @return \Psr\Log\LoggerInterface */ public function getLogger() { $this->logger = $this->logger ?: app(LoggerInterface::class); return $this->logger; } /** * Set monolog/logger instance. * * @return $this */ public function setLogger(LoggerInterface $logger): static { $this->logger = $logger; return $this; } /** * Setup search keyword. */ protected function setupKeyword(string $value): string { if ($this->config->isSmartSearch()) { $keyword = '%'.$value.'%'; if ($this->config->isWildcard()) { $keyword = Helper::wildcardLikeString($value); } // remove escaping slash added on js script request return str_replace('\\', '%', $keyword); } return $value; } /** * Get column name to be used for filtering and sorting. */ protected function getColumnName(int $index, bool $wantsAlias = false): ?string { $column = $this->request->columnName($index); if (is_null($column)) { return null; } // DataTables is using make(false) if (is_numeric($column)) { $column = $this->getColumnNameByIndex($index); } if (Str::contains(Str::upper($column), ' AS ')) { $column = Helper::extractColumnName($column, $wantsAlias); } return $column; } /** * Get column name by order column index. */ protected function getColumnNameByIndex(int $index): string { $name = (isset($this->columns[$index]) && $this->columns[$index] != '*') ? $this->columns[$index] : $this->getPrimaryKeyName(); return in_array($name, $this->extraColumns, true) ? $this->getPrimaryKeyName() : $name; } /** * If column name could not be resolved then use primary key. */ protected function getPrimaryKeyName(): string { return 'id'; } } src/ApiResourceDataTable.php 0000644 00000002666 15060250030 0012030 0 ustar 00 <?php namespace Yajra\DataTables; use Illuminate\Http\Resources\Json\AnonymousResourceCollection; class ApiResourceDataTable extends CollectionDataTable { /** * Can the DataTable engine be created with these parameters. * * @param mixed $source * @return bool */ public static function canCreate($source) { return $source instanceof AnonymousResourceCollection; } /** * Factory method, create and return an instance for the DataTable engine. * * @param \Illuminate\Http\Resources\Json\AnonymousResourceCollection<array-key, array>|array $source * @return ApiResourceDataTable|DataTableAbstract */ public static function create($source) { return parent::create($source); } /** * CollectionEngine constructor. * * @param \Illuminate\Http\Resources\Json\AnonymousResourceCollection<array-key, array> $resourceCollection */ public function __construct(AnonymousResourceCollection $resourceCollection) { /** @var \Illuminate\Support\Collection<(int|string), array> $collection */ $collection = collect($resourceCollection)->pluck('resource'); $this->request = app('datatables.request'); $this->config = app('datatables.config'); $this->collection = $collection; $this->original = $collection; $this->columns = array_keys($this->serialize($collection->first())); } } src/Facades/DataTables.php 0000644 00000001147 15060250030 0011370 0 ustar 00 <?php namespace Yajra\DataTables\Facades; use Illuminate\Support\Facades\Facade; /** * @mixin \Yajra\DataTables\DataTables * * @method static \Yajra\DataTables\EloquentDataTable eloquent($builder) * @method static \Yajra\DataTables\QueryDataTable query($builder) * @method static \Yajra\DataTables\CollectionDataTable collection($collection) * * @see \Yajra\DataTables\DataTables */ class DataTables extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return 'datatables'; } } src/lumen.php 0000644 00000001051 15060250030 0007150 0 ustar 00 <?php if (! function_exists('config_path')) { /** * Get the configuration path. * * @param string $path * @return string */ function config_path($path = '') { return app()->basePath().'/config'.($path ? '/'.$path : $path); } } if (! function_exists('public_path')) { /** * Return the path to public dir. * * @param null $path * @return string */ function public_path($path = null) { return rtrim((string) app()->basePath('public/'.$path), '/'); } } src/CollectionDataTable.php 0000644 00000021405 15060250030 0011672 0 ustar 00 <?php namespace Yajra\DataTables; use Closure; use Exception; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Http\JsonResponse; use Illuminate\Http\Resources\Json\AnonymousResourceCollection; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Str; class CollectionDataTable extends DataTableAbstract { /** * Collection object. * * @var \Illuminate\Support\Collection<array-key, array> */ public Collection $original; /** * The offset of the first record in the full dataset. */ private int $offset = 0; /** * CollectionEngine constructor. * * @param \Illuminate\Support\Collection<array-key, array> $collection */ public function __construct(public Collection $collection) { $this->request = app('datatables.request'); $this->config = app('datatables.config'); $this->original = $this->collection; $this->columns = array_keys($this->serialize($this->collection->first())); } /** * Serialize collection. */ protected function serialize(mixed $collection): array { return $collection instanceof Arrayable ? $collection->toArray() : (array) $collection; } /** * Can the DataTable engine be created with these parameters. * * @param mixed $source * @return bool */ public static function canCreate($source) { return is_array($source) || $source instanceof Collection; } /** * Factory method, create and return an instance for the DataTable engine. * * @param AnonymousResourceCollection|array|\Illuminate\Support\Collection<array-key, array> $source * @return static */ public static function create($source) { if (is_array($source)) { $source = new Collection($source); } return parent::create($source); } /** * Count results. */ public function count(): int { return $this->collection->count(); } /** * Perform column search. */ public function columnSearch(): void { for ($i = 0, $c = count($this->request->columns()); $i < $c; $i++) { $column = $this->getColumnName($i); if (is_null($column)) { continue; } if (! $this->request->isColumnSearchable($i) || $this->isBlacklisted($column)) { continue; } $regex = $this->request->isRegex($i); $keyword = $this->request->columnKeyword($i); $this->collection = $this->collection->filter( function ($row) use ($column, $keyword, $regex) { $data = $this->serialize($row); /** @var string $value */ $value = Arr::get($data, $column); if ($this->config->isCaseInsensitive()) { if ($regex) { return preg_match('/'.$keyword.'/i', $value) == 1; } return str_contains(Str::lower($value), Str::lower($keyword)); } if ($regex) { return preg_match('/'.$keyword.'/', $value) == 1; } return str_contains($value, $keyword); } ); } } /** * Perform pagination. */ public function paging(): void { $offset = $this->request->start() - $this->offset; $length = $this->request->length() > 0 ? $this->request->length() : 10; $this->collection = $this->collection->slice($offset, $length); } /** * Organizes works. * * @throws \Exception */ public function make(bool $mDataSupport = true): JsonResponse { try { $this->totalRecords = $this->totalCount(); if ($this->totalRecords) { $results = $this->results(); $processed = $this->processResults($results, $mDataSupport); $output = $this->transform($results, $processed); $this->collection = collect($output); $this->ordering(); $this->filterRecords(); $this->paginate(); $this->revertIndexColumn($mDataSupport); } return $this->render($this->collection->values()->all()); } catch (Exception $exception) { return $this->errorResponse($exception); } } /** * Get results. * * @return \Illuminate\Support\Collection<array-key, array> */ public function results(): Collection { return $this->collection; } /** * Revert transformed DT_RowIndex back to its original values. * * @param bool $mDataSupport */ private function revertIndexColumn($mDataSupport): void { if ($this->columnDef['index']) { $indexColumn = config('datatables.index_column', 'DT_RowIndex'); $index = $mDataSupport ? $indexColumn : 0; $start = $this->request->start(); $this->collection->transform(function ($data) use ($index, &$start) { $data[$index] = ++$start; return $data; }); } } /** * Define the offset of the first item of the collection with respect to * the FULL dataset the collection was sliced from. It effectively allows the * collection to be "pre-sliced". * * @return static */ public function setOffset(int $offset): self { $this->offset = $offset; return $this; } /** * Perform global search for the given keyword. */ protected function globalSearch(string $keyword): void { $keyword = $this->config->isCaseInsensitive() ? Str::lower($keyword) : $keyword; $this->collection = $this->collection->filter(function ($row) use ($keyword) { $data = $this->serialize($row); foreach ($this->request->searchableColumnIndex() as $index) { $column = $this->getColumnName($index); $value = Arr::get($data, $column); if (! is_string($value)) { continue; } else { $value = $this->config->isCaseInsensitive() ? Str::lower($value) : $value; } if (Str::contains($value, $keyword)) { return true; } } return false; }); } /** * Perform default query orderBy clause. */ protected function defaultOrdering(): void { $criteria = $this->request->orderableColumns(); if (! empty($criteria)) { $sorter = $this->getSorter($criteria); $this->collection = $this->collection ->map(fn ($data) => Arr::dot($data)) ->sort($sorter) ->map(function ($data) { foreach ($data as $key => $value) { unset($data[$key]); Arr::set($data, $key, $value); } return $data; }); } } /** * Get array sorter closure. */ protected function getSorter(array $criteria): Closure { return function ($a, $b) use ($criteria) { foreach ($criteria as $orderable) { $column = $this->getColumnName($orderable['column']); $direction = $orderable['direction']; if ($direction === 'desc') { $first = $b; $second = $a; } else { $first = $a; $second = $b; } if (is_numeric($first[$column] ?? null) && is_numeric($second[$column] ?? null)) { if ($first[$column] < $second[$column]) { $cmp = -1; } elseif ($first[$column] > $second[$column]) { $cmp = 1; } else { $cmp = 0; } } elseif ($this->config->isCaseInsensitive()) { $cmp = strnatcasecmp($first[$column] ?? '', $second[$column] ?? ''); } else { $cmp = strnatcmp($first[$column] ?? '', $second[$column] ?? ''); } if ($cmp != 0) { return $cmp; } } // all elements were equal return 0; }; } /** * Resolve callback parameter instance. * * @return array<int|string, mixed> */ protected function resolveCallbackParameter(): array { return [$this, false]; } } src/DataTablesServiceProvider.php 0000644 00000004307 15060250030 0013077 0 ustar 00 <?php namespace Yajra\DataTables; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Str; use Yajra\DataTables\Utilities\Config; use Yajra\DataTables\Utilities\Request; class DataTablesServiceProvider extends ServiceProvider { /** * Register the service provider. * * @return void */ public function register() { if ($this->isLumen()) { require_once 'lumen.php'; } $this->setupAssets(); $this->app->alias('datatables', DataTables::class); $this->app->singleton('datatables', fn () => new DataTables); $this->app->singleton('datatables.request', fn () => new Request); $this->app->singleton('datatables.config', Config::class); } /** * Boot the instance, add macros for datatable engines. * * @return void */ public function boot() { $engines = (array) config('datatables.engines'); foreach ($engines as $engine => $class) { $engine = Str::camel($engine); if (! method_exists(DataTables::class, $engine) && ! DataTables::hasMacro($engine)) { DataTables::macro($engine, function () use ($class) { $canCreate = [$class, 'canCreate']; if (is_callable($canCreate) && ! call_user_func_array($canCreate, func_get_args())) { throw new \InvalidArgumentException(); } $create = [$class, 'create']; if (is_callable($create)) { return call_user_func_array($create, func_get_args()); } }); } } } /** * Setup package assets. * * @return void */ protected function setupAssets() { $this->mergeConfigFrom($config = __DIR__.'/config/datatables.php', 'datatables'); if ($this->app->runningInConsole()) { $this->publishes([$config => config_path('datatables.php')], 'datatables'); } } /** * Check if app uses Lumen. * * @return bool */ protected function isLumen() { return Str::contains($this->app->version(), 'Lumen'); } } src/Contracts/Formatter.php 0000644 00000000342 15060250030 0011735 0 ustar 00 <?php namespace Yajra\DataTables\Contracts; interface Formatter { /** * @param array|\Illuminate\Database\Eloquent\Model|object $row * @return string */ public function format(mixed $value, $row); } src/Contracts/DataTable.php 0000644 00000002120 15060250030 0011607 0 ustar 00 <?php namespace Yajra\DataTables\Contracts; use Illuminate\Http\JsonResponse; use Illuminate\Support\Collection; interface DataTable { /** * Get results. * * @return \Illuminate\Support\Collection<int, array> */ public function results(): Collection; /** * Count results. */ public function count(): int; /** * Count total items. */ public function totalCount(): int; /** * Set auto filter off and run your own filter. * Overrides global search. * * @return static */ public function filter(callable $callback, bool $globalSearch = false): self; /** * Perform global search. */ public function filtering(): void; /** * Perform column search. */ public function columnSearch(): void; /** * Perform pagination. */ public function paging(): void; /** * Perform sorting of columns. */ public function ordering(): void; /** * Organizes works. */ public function make(bool $mDataSupport = true): JsonResponse; } src/config/datatables.php 0000644 00000007662 15060250030 0011417 0 ustar 00 <?php return [ /* * DataTables search options. */ 'search' => [ /* * Smart search will enclose search keyword with wildcard string "%keyword%". * SQL: column LIKE "%keyword%" */ 'smart' => true, /* * Multi-term search will explode search keyword using spaces resulting into multiple term search. */ 'multi_term' => true, /* * Case insensitive will search the keyword in lower case format. * SQL: LOWER(column) LIKE LOWER(keyword) */ 'case_insensitive' => true, /* * Wild card will add "%" in between every characters of the keyword. * SQL: column LIKE "%k%e%y%w%o%r%d%" */ 'use_wildcards' => false, /* * Perform a search which starts with the given keyword. * SQL: column LIKE "keyword%" */ 'starts_with' => false, ], /* * DataTables internal index id response column name. */ 'index_column' => 'DT_RowIndex', /* * List of available builders for DataTables. * This is where you can register your custom dataTables builder. */ 'engines' => [ 'eloquent' => Yajra\DataTables\EloquentDataTable::class, 'query' => Yajra\DataTables\QueryDataTable::class, 'collection' => Yajra\DataTables\CollectionDataTable::class, 'resource' => Yajra\DataTables\ApiResourceDataTable::class, ], /* * DataTables accepted builder to engine mapping. * This is where you can override which engine a builder should use * Note, only change this if you know what you are doing! */ 'builders' => [ //Illuminate\Database\Eloquent\Relations\Relation::class => 'eloquent', //Illuminate\Database\Eloquent\Builder::class => 'eloquent', //Illuminate\Database\Query\Builder::class => 'query', //Illuminate\Support\Collection::class => 'collection', ], /* * Nulls last sql pattern for PostgreSQL & Oracle. * For MySQL, use 'CASE WHEN :column IS NULL THEN 1 ELSE 0 END, :column :direction' */ 'nulls_last_sql' => ':column :direction NULLS LAST', /* * User friendly message to be displayed on user if error occurs. * Possible values: * null - The exception message will be used on error response. * 'throw' - Throws a \Yajra\DataTables\Exceptions\Exception. Use your custom error handler if needed. * 'custom message' - Any friendly message to be displayed to the user. You can also use translation key. */ 'error' => env('DATATABLES_ERROR', null), /* * Default columns definition of dataTable utility functions. */ 'columns' => [ /* * List of columns hidden/removed on json response. */ 'excess' => ['rn', 'row_num'], /* * List of columns to be escaped. If set to *, all columns are escape. * Note: You can set the value to empty array to disable XSS protection. */ 'escape' => '*', /* * List of columns that are allowed to display html content. * Note: Adding columns to list will make us available to XSS attacks. */ 'raw' => ['action'], /* * List of columns are forbidden from being searched/sorted. */ 'blacklist' => ['password', 'remember_token'], /* * List of columns that are only allowed fo search/sort. * If set to *, all columns are allowed. */ 'whitelist' => '*', ], /* * JsonResponse header and options config. */ 'json' => [ 'header' => [], 'options' => 0, ], /* * Default condition to determine if a parameter is a callback or not. * Callbacks needs to start by those terms, or they will be cast to string. */ 'callback' => ['$', '$.', 'function'], ]; src/EloquentDataTable.php 0000644 00000021122 15060250030 0011367 0 ustar 00 <?php namespace Yajra\DataTables; use Illuminate\Contracts\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasOneOrMany; use Illuminate\Database\Eloquent\Relations\HasOneThrough; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\Relation; use Yajra\DataTables\Exceptions\Exception; /** * @property EloquentBuilder $query */ class EloquentDataTable extends QueryDataTable { /** * EloquentEngine constructor. */ public function __construct(Model|EloquentBuilder $model) { $builder = match (true) { $model instanceof Model => $model->newQuery(), $model instanceof Relation => $model->getQuery(), $model instanceof EloquentBuilder => $model, }; parent::__construct($builder->getQuery()); $this->query = $builder; } /** * Can the DataTable engine be created with these parameters. * * @param mixed $source */ public static function canCreate($source): bool { return $source instanceof EloquentBuilder; } /** * Add columns in collection. * * @param bool|int $order * @return $this */ public function addColumns(array $names, $order = false) { foreach ($names as $name => $attribute) { if (is_int($name)) { $name = $attribute; } $this->addColumn($name, fn ($model) => $model->getAttribute($attribute), is_int($order) ? $order++ : $order); } return $this; } /** * If column name could not be resolved then use primary key. */ protected function getPrimaryKeyName(): string { return $this->query->getModel()->getKeyName(); } /** * {@inheritDoc} */ protected function compileQuerySearch($query, string $column, string $keyword, string $boolean = 'or', bool $nested = false): void { if (substr_count($column, '.') > 1) { $parts = explode('.', $column); $firstRelation = array_shift($parts); $column = implode('.', $parts); if ($this->isMorphRelation($firstRelation)) { $query->{$boolean.'WhereHasMorph'}( $firstRelation, '*', function (EloquentBuilder $query) use ($column, $keyword) { parent::compileQuerySearch($query, $column, $keyword, ''); } ); } else { $query->{$boolean.'WhereHas'}($firstRelation, function (EloquentBuilder $query) use ($column, $keyword) { self::compileQuerySearch($query, $column, $keyword, '', true); }); } return; } $parts = explode('.', $column); $newColumn = array_pop($parts); $relation = implode('.', $parts); if (! $nested && $this->isNotEagerLoaded($relation)) { parent::compileQuerySearch($query, $column, $keyword, $boolean); return; } if ($this->isMorphRelation($relation)) { $query->{$boolean.'WhereHasMorph'}( $relation, '*', function (EloquentBuilder $query) use ($newColumn, $keyword) { parent::compileQuerySearch($query, $newColumn, $keyword, ''); } ); } else { $query->{$boolean.'WhereHas'}($relation, function (EloquentBuilder $query) use ($newColumn, $keyword) { parent::compileQuerySearch($query, $newColumn, $keyword, ''); }); } } /** * Check if a relation was not used on eager loading. * * @param string $relation * @return bool */ protected function isNotEagerLoaded($relation) { return ! $relation || ! array_key_exists($relation, $this->query->getEagerLoads()) || $relation === $this->query->getModel()->getTable(); } /** * Check if a relation is a morphed one or not. * * @param string $relation * @return bool */ protected function isMorphRelation($relation) { $isMorph = false; if ($relation !== null && $relation !== '') { $relationParts = explode('.', $relation); $firstRelation = array_shift($relationParts); $model = $this->query->getModel(); $isMorph = method_exists($model, $firstRelation) && $model->$firstRelation() instanceof MorphTo; } return $isMorph; } /** * Resolve the proper column name be used. * * * @throws \Yajra\DataTables\Exceptions\Exception */ protected function resolveRelationColumn(string $column): string { $parts = explode('.', $column); $columnName = array_pop($parts); $relation = implode('.', $parts); if ($this->isNotEagerLoaded($relation)) { return $column; } return $this->joinEagerLoadedColumn($relation, $columnName); } /** * Join eager loaded relation and get the related column name. * * @param string $relation * @param string $relationColumn * @return string * * @throws \Yajra\DataTables\Exceptions\Exception */ protected function joinEagerLoadedColumn($relation, $relationColumn) { $table = ''; $lastQuery = $this->query; foreach (explode('.', $relation) as $eachRelation) { $model = $lastQuery->getRelation($eachRelation); switch (true) { case $model instanceof BelongsToMany: $pivot = $model->getTable(); $pivotPK = $model->getExistenceCompareKey(); $pivotFK = $model->getQualifiedParentKeyName(); $this->performJoin($pivot, $pivotPK, $pivotFK); $related = $model->getRelated(); $table = $related->getTable(); $tablePK = $model->getRelatedPivotKeyName(); $foreign = $pivot.'.'.$tablePK; $other = $related->getQualifiedKeyName(); $lastQuery->addSelect($table.'.'.$relationColumn); $this->performJoin($table, $foreign, $other); break; case $model instanceof HasOneThrough: $pivot = explode('.', $model->getQualifiedParentKeyName())[0]; // extract pivot table from key $pivotPK = $pivot.'.'.$model->getFirstKeyName(); $pivotFK = $model->getQualifiedLocalKeyName(); $this->performJoin($pivot, $pivotPK, $pivotFK); $related = $model->getRelated(); $table = $related->getTable(); $tablePK = $model->getSecondLocalKeyName(); $foreign = $pivot.'.'.$tablePK; $other = $related->getQualifiedKeyName(); $lastQuery->addSelect($lastQuery->getModel()->getTable().'.*'); break; case $model instanceof HasOneOrMany: $table = $model->getRelated()->getTable(); $foreign = $model->getQualifiedForeignKeyName(); $other = $model->getQualifiedParentKeyName(); break; case $model instanceof BelongsTo: $table = $model->getRelated()->getTable(); $foreign = $model->getQualifiedForeignKeyName(); $other = $model->getQualifiedOwnerKeyName(); break; default: throw new Exception('Relation '.$model::class.' is not yet supported.'); } $this->performJoin($table, $foreign, $other); $lastQuery = $model->getQuery(); } return $table.'.'.$relationColumn; } /** * Perform join query. * * @param string $table * @param string $foreign * @param string $other * @param string $type */ protected function performJoin($table, $foreign, $other, $type = 'left'): void { $joins = []; $builder = $this->getBaseQueryBuilder(); foreach ($builder->joins ?? [] as $join) { $joins[] = $join->table; } if (! in_array($table, $joins)) { $this->getBaseQueryBuilder()->join($table, $foreign, '=', $other, $type); } } } src/Exceptions/Exception.php 0000644 00000000126 15060250030 0012111 0 ustar 00 <?php namespace Yajra\DataTables\Exceptions; class Exception extends \Exception { } LICENSE.md 0000644 00000002117 15060250030 0006140 0 ustar 00 (The MIT License) Copyright (c) 2013-2022 Arjay Angeles <aqangeles@gmail.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rector.php 0000644 00000001032 15060250030 0006536 0 ustar 00 <?php declare(strict_types=1); use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; use Rector\Config\RectorConfig; use Rector\Set\ValueObject\LevelSetList; return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ __DIR__.'/src', __DIR__.'/tests', ]); // register a single rule $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); // define sets of rules $rectorConfig->sets([ LevelSetList::UP_TO_PHP_82, ]); }; UPGRADE.md 0000644 00000002441 15060250030 0006145 0 ustar 00 # UPGRADE GUIDE ## Upgrading from v9.x to v10.x - `ApiResourceDataTable` support dropped, use `CollectionDataTable` instead. - `queryBuilder()` deprecated method removed, use `query()` instead. - Methods signature were updated to PHP8 syntax, adjust as needed if you extended the package. ## Upgrading from v8.x to v9.x No breaking changes with only a bump on php version requirements. ## Upgrading from v7.x to v8.x There are breaking changes since DataTables v8.x. If you are upgrading from v7.x to v8.x, please see [upgrade guide](https://yajrabox.com/docs/laravel-datatables/master/upgrade). ## Upgrading from v6.x to v7.x - composer require yajra/laravel-datatables-oracle - composer require yajra/laravel-datatables-buttons - php artisan vendor:publish --tag=datatables --force - php artisan vendor:publish --tag=datatables-buttons --force ## Upgrading from v5.x to v6.x - Change all occurrences of `yajra\Datatables` to `Yajra\Datatables`. (Use Sublime's find and replace all for faster update). - Remove `Datatables` facade registration. - Temporarily comment out `Yajra\Datatables\DatatablesServiceProvider`. - Update package version on your composer.json and use `yajra/laravel-datatables-oracle: ~6.0` - Uncomment the provider `Yajra\Datatables\DatatablesServiceProvider`. CHANGELOG.md 0000644 00000001055 15060250030 0006345 0 ustar 00 # Laravel DataTables ## CHANGELOG ### [Unreleased] ### [v11.1.1](https://github.com/yajra/laravel-datatables/compare/v11.1.0...v11.1.1) - 2024-04-16 - fix: mariadb support for scout search #3146 ### [v11.1.0](https://github.com/yajra/laravel-datatables/compare/v11.0.0...v11.1.0) - 2024-04-16 - feat: Optimize simple queries #3135 - fix: #3133 ### [v11.0.0](https://github.com/yajra/laravel-datatables/compare/v11.0.0...master) - 2024-03-14 - Laravel 11 support [Unreleased]: https://github.com/yajra/laravel-datatables/compare/v11.0.0...master composer.json 0000644 00000005243 15060250030 0007261 0 ustar 00 { "name": "yajra/laravel-datatables-oracle", "description": "jQuery DataTables API for Laravel", "keywords": [ "yajra", "laravel", "dataTables", "jquery" ], "license": "MIT", "authors": [ { "name": "Arjay Angeles", "email": "aqangeles@gmail.com" } ], "require": { "php": "^8.2", "illuminate/database": "^11", "illuminate/filesystem": "^11", "illuminate/http": "^11", "illuminate/support": "^11", "illuminate/view": "^11" }, "require-dev": { "algolia/algoliasearch-client-php": "^3.4.1", "larastan/larastan": "^2.9.1", "laravel/pint": "^1.14", "laravel/scout": "^10.8.3", "meilisearch/meilisearch-php": "^1.6.1", "orchestra/testbench": "^9", "rector/rector": "^1.0" }, "suggest": { "yajra/laravel-datatables-export": "Plugin for server-side exporting using livewire and queue worker.", "yajra/laravel-datatables-buttons": "Plugin for server-side exporting of dataTables.", "yajra/laravel-datatables-html": "Plugin for server-side HTML builder of dataTables.", "yajra/laravel-datatables-fractal": "Plugin for server-side response using Fractal.", "yajra/laravel-datatables-editor": "Plugin to use DataTables Editor (requires a license)." }, "autoload": { "psr-4": { "Yajra\\DataTables\\": "src/" }, "files": [ "src/helper.php" ] }, "autoload-dev": { "psr-4": { "Yajra\\DataTables\\Tests\\": "tests/" } }, "extra": { "branch-alias": { "dev-master": "11.x-dev" }, "laravel": { "providers": [ "Yajra\\DataTables\\DataTablesServiceProvider" ], "aliases": { "DataTables": "Yajra\\DataTables\\Facades\\DataTables" } } }, "config": { "sort-packages": true, "allow-plugins": { "php-http/discovery": true } }, "scripts": { "test": "./vendor/bin/phpunit", "pint": "./vendor/bin/pint", "rector": "./vendor/bin/rector", "stan": "./vendor/bin/phpstan analyse --memory-limit=2G --ansi --no-progress --no-interaction --configuration=phpstan.neon.dist", "pr": [ "@rector", "@pint", "@stan", "@test" ] }, "minimum-stability": "dev", "prefer-stable": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/yajra" } ] } pint.json 0000644 00000000034 15060250030 0006375 0 ustar 00 { "preset": "laravel" } README.md 0000644 00000012701 15060250030 0006013 0 ustar 00 # jQuery DataTables API for Laravel [](https://gitter.im/yajra/laravel-datatables?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://www.paypal.me/yajra) [](https://www.patreon.com/bePatron?u=4521203) [](http://laravel.com) [](https://packagist.org/packages/yajra/laravel-datatables-oracle) [](https://github.com/yajra/laravel-datatables/actions/workflows/continuous-integration.yml) [](https://github.com/yajra/laravel-datatables/actions/workflows/static-analysis.yml) [](https://packagist.org/packages/yajra/laravel-datatables-oracle) [](https://packagist.org/packages/yajra/laravel-datatables-oracle) Laravel package for handling [server-side](https://www.datatables.net/manual/server-side) works of [DataTables](http://datatables.net) jQuery Plugin via [AJAX option](https://datatables.net/reference/option/ajax) by using Eloquent ORM, Fluent Query Builder or Collection. ```php use Yajra\DataTables\Facades\DataTables; return DataTables::eloquent(User::query())->toJson(); return DataTables::query(DB::table('users'))->toJson(); return DataTables::collection(User::all())->toJson(); return DataTables::make(User::query())->toJson(); return DataTables::make(DB::table('users'))->toJson(); return DataTables::make(User::all())->toJson(); ``` ## Sponsors <a href="https://editor.datatables.net?utm_source=laravel-datatables&utm_medium=github_readme&utm_campaign=logo"> <img src="http://datatables.net/media/images/logo.png" alt="DataTables" height="64"> </a> <a href="https://jb.gg/OpenSourceSupport"> <img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains.com" height="128"> </a> <a href="https://blackfire.io/docs/introduction?utm_source=laravel-datatables&utm_medium=github_readme&utm_campaign=logo"> <img src="https://i.imgur.com/zR8rsqk.png" alt="Blackfire.io" height="64"> </a> ## Requirements - [PHP >= 8.2](http://php.net/) - [Laravel Framework](https://github.com/laravel/framework) - [jQuery DataTables](http://datatables.net/) ## Documentations - [Github Docs](https://github.com/yajra/laravel-datatables-docs) - [Laravel DataTables Quick Starter](https://yajrabox.com/docs/laravel-datatables/master/quick-starter) - [Laravel DataTables Documentation](https://yajrabox.com/docs/laravel-datatables) ## Laravel Version Compatibility | Laravel | Package | |:--------|:---------| | 4.2.x | 3.x | | 5.0.x | 6.x | | 5.1.x | 6.x | | 5.2.x | 6.x | | 5.3.x | 6.x | | 5.4.x | 7.x, 8.x | | 5.5.x | 8.x | | 5.6.x | 8.x | | 5.7.x | 8.x | | 5.8.x | 9.x | | 6.x | 9.x | | 7.x | 9.x | | 8.x | 9.x | | 9.x | 10.x | | 10.x | 10.x | | 11.x | 11.x | ## Quick Installation ```bash composer require yajra/laravel-datatables-oracle:"^11" ``` #### Service Provider & Facade (Optional on Laravel 5.5+) Register provider and facade on your `config/app.php` file. ```php 'providers' => [ ..., Yajra\DataTables\DataTablesServiceProvider::class, ] 'aliases' => [ ..., 'DataTables' => Yajra\DataTables\Facades\DataTables::class, ] ``` #### Configuration (Optional) ```bash php artisan vendor:publish --provider="Yajra\DataTables\DataTablesServiceProvider" ``` And that's it! Start building out some awesome DataTables! ## Debugging Mode To enable debugging mode, just set `APP_DEBUG=true` and the package will include the queries and inputs used when processing the table. **IMPORTANT:** Please make sure that APP_DEBUG is set to false when your app is on production. ## PHP ARTISAN SERVE BUG Please avoid using `php artisan serve` when developing with the package. There are known bugs when using this where Laravel randomly returns a redirect and 401 (Unauthorized) if the route requires authentication and a 404 NotFoundHttpException on valid routes. It is advised to use [Homestead](https://laravel.com/docs/5.4/homestead) or [Valet](https://laravel.com/docs/5.4/valet) when working with the package. ## Contributing Please see [CONTRIBUTING](https://github.com/yajra/laravel-datatables/blob/master/.github/CONTRIBUTING.md) for details. ## Security If you discover any security related issues, please email [aqangeles@gmail.com](mailto:aqangeles@gmail.com) instead of using the issue tracker. ## Credits - [Arjay Angeles](https://github.com/yajra) - [bllim/laravel4-datatables-package](https://github.com/bllim/laravel4-datatables-package) - [All Contributors](https://github.com/yajra/laravel-datatables/graphs/contributors) ## License The MIT License (MIT). Please see [License File](https://github.com/yajra/laravel-datatables/blob/master/LICENSE.md) for more information. CONDUCT.md 0000644 00000012243 15060250030 0006156 0 ustar 00 # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others’ private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning Community Impact: A violation through a single incident or series of actions. Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban Community Impact: A serious violation of community standards, including sustained inappropriate behavior. Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. Consequence: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. Community Impact Guidelines were inspired by Mozilla’s code of conduct enforcement ladder. For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
| ver. 1.4 |
Github
|
.
| PHP 8.2.29 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка