voyager-admin/voyager

View on GitHub
src/Traits/Bread/Browsable.php

Summary

Maintainability
C
1 day
Test Coverage
F
46%
<?php

namespace Voyager\Admin\Traits\Bread;

use DB;
use Illuminate\Database\Eloquent\Relations;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Voyager\Admin\Classes\Bread;
use Voyager\Admin\Classes\Layout;
use Voyager\Admin\Classes\Formfield;
use Voyager\Admin\Facades\Voyager as VoyagerFacade;

/**
 * @property bool $uses_soft_deletes
 */
trait Browsable
{
    public function loadSoftDeletesQuery(Bread $bread, Layout $layout, string $softdeleted, mixed $query): mixed
    {
        $this->uses_soft_deletes = $bread->usesSoftDeletes();
        if (!isset($layout->options->soft_deletes) || !$layout->options->soft_deletes || !$this->uses_soft_deletes) {
            $this->uses_soft_deletes = false;

            return $query;
        }
        if ($softdeleted == 'show') {
            $query = $query->withTrashed();
        } elseif ($softdeleted == 'only') {
            $query = $query->onlyTrashed();
        }

        return $query;
    }

    public function globalSearchQuery(?string $global, Layout $layout, string $locale, mixed $query): mixed
    {
        if (!empty($global)) {
            $query = $query->where(function ($query) use ($global, $layout, $locale) {
                $layout->searchableFormfields()->each(function ($formfield) use (&$query, $global, $locale) {
                    $query = $this->queryColumn($query, $formfield, $global, $locale, true);
                });
            });
        }

        return $query;
    }

    public function columnSearchQuery(array $filters, Layout $layout, mixed $query, string $locale, array &$warnings): mixed
    {
        collect(array_filter($filters))->each(function (mixed $filter, string $column) use ($layout, &$query, $locale, &$warnings) {
            $formfield = $layout->getFormfieldByColumn($column);
            if (!$formfield) {
                $warnings[] = __('voyager::bread.column_does_not_exist_search', ['column' => $column]);

                return;
            }

            $query = $this->queryColumn($query, $formfield, $filter, $locale);
        });

        return $query;
    }

    public function applyCustomFilter(Bread $bread, Layout $layout, mixed $filter, mixed $query): mixed
    {
        if (!is_null($filter) && is_array($filter)) {
            // Validate filter exists in layout
            if (collect($layout->options->filters)->where('column', $filter['column'])->where('operator', $filter['operator'])->where('value', $filter['value'])->count() > 0) {
                if ($filter['column']) {
                    $query = $query->where($filter['column'], $filter['operator'], $filter['value']);
                }
            }
        }

        return $query;
    }

    public function applyCustomScope(Bread $bread, Layout $layout, mixed $filter, mixed $query, array &$warnings): mixed
    {
        if (!is_null($filter) && is_array($filter)) {
            // Validate filter exists in layout
            if (collect($layout->options->filters)->where('column', $filter['column'])->where('operator', $filter['operator'])->where('value', $filter['value'])->count() > 0) {
                if ($filter['column'] === null) {
                    if (method_exists($query, $filter['value'])) {
                        $query = $query->{$filter['value']}();
                    } else {
                        $warnings[] = __('voyager::bread.scope_does_not_exist', ['scope' => $filter['value']]);
                    }
                }
            }
        }

        return $query;
    }

    public function orderQuery(Layout $layout, mixed $direction, mixed $order, mixed $query, string $locale, array &$warnings): mixed
    {
        if (!empty($direction) && !empty($order)) {
            $formfield = $layout->getFormfieldByColumn($order);
            if ($formfield && $formfield->column->type == 'column') {
                if ($layout->getFormfieldByColumn($order)->translatable ?? false) {
                    $query = $query->orderBy(DB::raw('lower('.$order.'->"$.'.$locale.'")'), $direction);
                } else {
                    $query = $query->orderBy($order, $direction);
                }
            } elseif ($formfield && $formfield->column->type == 'relationship') {
                $warnings[] = __('voyager::bread.can_not_order_by_relationship', ['column' => $formfield->column->column]);
            } else {
                $warnings[] =  __('voyager::bread.column_does_not_exist_order', ['column' => $order]);
            }
        }

        return $query;
    }

    public function eagerLoadRelationships(Layout $layout, mixed $query, array &$warnings): mixed
    {
        /*$instance = $query->getModel()->newInstance();
        $layout->getFormfieldsByColumnType('relationship')->pluck('column.column')->each(function ($relationship) use (&$query, &$warnings, $instance) {
            list($method) = explode('.', $relationship);

            if (method_exists($instance, $method)) {
                $query = $query->with($method);
            } else {
                $warnings[] = __('voyager::bread.relationship_does_not_exist', ['relationship' => $relationship]);
            }
        });*/

        return $query;
    }

    public function transformResults(Layout $layout, bool $translatable, mixed $query, ?string $global, mixed $filters): mixed
    {
        return $query->transform(function ($item) use ($translatable, $layout) {
            $item->primary_key = $item->getKey();
            if ($translatable) {
                $item->dontTranslate();
            }
            // Add soft-deleted property
            $item->is_soft_deleted = $this->uses_soft_deletes ? $item->trashed() : false;

            $layout->formfields->each(function ($formfield) use (&$item) {
                $column = $formfield->column->column;
                if ($formfield->column->type == 'relationship') {
                    $method = $prop = null;
                    $computed = false;
                    if (Str::contains($column, '.computed.')) {
                        list($method, , $prop) = explode('.', $column);
                        $computed = true;
                    } else {
                        list($method, $prop) = explode('.', $column);
                    }
                    
                    if ($computed) {
                        $item->{$column} = $item->{$method}->transform(function ($value) use ($prop) {
                            return [
                                'key'   => $value->getKey(),
                                'value' => $value->{$prop},
                            ];
                        });
                    } else {
                        $key = $item->{$method}()->getRelated()->getKeyName();
                        $table = $item->{$method}()->getRelated()->getTable();
                        $item->{$column} = $item->{$method}()->selectRaw("$table.$prop, $table.$key")->get()->transform(function ($item) use ($prop, $key) {
                            return [
                                'key'   => $item->{$key},
                                'value' => $item->{$prop},
                            ];
                        })->values();
                    }
                } elseif ($formfield->translatable ?? false) {
                    $value = $item->{$column};
                    if (is_string($value)) {
                        $value = json_decode($value) ?? [];
                    } elseif (empty($value)) {
                        $value = [];
                    }
                    foreach ($value as $locale => $content) {
                        $value->{$locale} = $formfield->browse($content);
                    }

                    $item->{$column} = $value;
                } else {
                    $item->{$column} = $formfield->browse($item->{$column});
                }
            });

            return $item;
        });
    }

    private function queryColumn(mixed $query, Formfield $formfield, mixed $filter, string $locale, bool $global = false): mixed
    {
        $filter = '%'.strtolower($filter).'%';
        $translatable = $formfield->translatable ?? false;
        $column = $formfield->column->column;

        if (method_exists($formfield, 'query')) {
            return $formfield->query($query, $filter, ($translatable ? $locale : null), $global);
        }

        if ($formfield->column->type == 'column' && $translatable) {
            $query = $query->{$global ? 'orWhereRaw' : 'whereRaw'}('LOWER(`'.$column.'->"$.'.$locale.'`) LIKE ?', [$filter]);
        } elseif ($formfield->column->type == 'column') {
            $query = $query->{$global ? 'orWhereRaw' : 'whereRaw'}('LOWER(`'.$column.'`) LIKE ?', [$filter]);
        } elseif ($formfield->column->type == 'relationship') {
            if ($global) {
                // TODO: We could skip global-searching relationships here
                // return $query;
            }
            list($name, $column) = explode('.', $formfield->column->column);

            $query = $query->{$global ? 'orWhereHas' : 'whereHas'}($name, function ($q) use ($column, $filter, $translatable, $locale) {
                if ($translatable) {
                    $q->whereRaw('LOWER(`'.$column.'->$.'.$locale.'`) LIKE ?', [$filter]);
                } else {
                    $q->whereRaw('LOWER(`'.$column.'`) LIKE ?', [$filter]);
                }
            });
        }

        return $query;
    }
}