diff --git a/app/Conductors/Conductor.php b/app/Conductors/Conductor.php new file mode 100644 index 0000000..aa0632b --- /dev/null +++ b/app/Conductors/Conductor.php @@ -0,0 +1,291 @@ +query = $conductor->class::query(); + } catch (\Throwable $e) { + throw new \Exception('Failed to create query builder instance for ' . $conductor->class . '.', 0, $e); + } + + // Scope query + $conductor->scope($conductor->query); + + // Filter request + $fields = $conductor->fields(new $conductor->class); + if(is_array($fields) == false) { + $fields = []; + } + + $params = $request->all(); + $filterFields = array_intersect_key($params, array_flip($fields)); + $conductor->filter($filterFields); + + // Sort request + $conductor->sort($request->input('sort', $conductor->sort)); + + // Get total + $total = $conductor->count(); + + // Paginate + $conductor->paginate($request->input('page', 1), $request->input('limit', -1)); + + // Limit fields + $limitFields = $request->input('fields'); + if($limitFields == null) { + $limitFields = $fields; + } else { + $limitFields = array_intersect($limitFields, $fields); + } + $conductor->limitFields($limitFields); + + $conductor->collection = $conductor->query->get(); + + // Transform and Includes + $includes = $conductor->includes; + if($request->has('includes')) { + $includes = explode(',', $request->input('includes')); + } + + $conductor->collection = $conductor->collection->map(function ($model) use($conductor, $includes) { + $conductor->includes($model, $includes); + $model = $conductor->transform($model); + + return $model; + }); + + return [$conductor->collection, $total]; + } + + final public static function model(Request $request, Model $model) { + $conductor_class = get_called_class(); + $conductor = new $conductor_class; + + $fields = $conductor->fields(new $conductor->class); + + // Limit fields + $limitFields = $fields; + if($request != null && $request->has('fields')) { + $requestFields = $request->input('fields'); + if($requestFields != null) { + $limitFields = array_intersect(explode(',', $requestFields), $fields); + } + } + + if(empty($limitFields) === false) { + $modelSubset = new $conductor->class; + foreach($limitFields as $field) { + $modelSubset->setAttribute($field, $model->$field); + } + $model = $modelSubset; + } + + // Includes + $includes = $conductor->includes; + if($request != null && $request->has('includes')) { + $includes = explode(',', $request->input('includes', '')); + } + $conductor->includes($model, $includes); + + // Transform + $model = $conductor->transform($model); + + return $model; + } + + final public function filterField(Builder $builder, string $field, mixed $value) { + // Split by comma, but respect quotation marks + if (is_string($value)) { + $values = preg_split('/(?query->where(function ($query) use ($field, $values) { + foreach ($values as $value) { + $value = trim($value); + + // Check if value has a prefix and remove it if it's a number + if (preg_match('/^([<>]=?)(\d+\.?\d*)$/', $value, $matches)) { + $prefix = $matches[1]; + $value = $matches[2]; + } else { + $prefix = ''; + } + + // If the value starts with '=', exact match + if (strpos($value, '=') === 0) { + $query->where($field, '=', substr($value, 1)); + } else { + // Otherwise, use LIKE with '%value%' + $query->where($field, 'LIKE', "%$value%"); + } + + // Apply the prefix to the query if the value is a number + if (is_numeric($value)) { + switch ($prefix) { + case '>': + $query->where($field, '>', $value); + break; + case '<': + $query->where($field, '<', $value); + break; + case '>=': + $query->where($field, '>=', $value); + break; + case '<=': + $query->where($field, '<=', $value); + break; + } + } + } + }); + } + + final public function collection(Collection $collection = null) { + if($collection != null) { + $this->collection = $collection; + } + + return $this->collection; + } + + final public function count() { + if($this->query != null) { + return $this->query->count(); + } + + return 0; + } + + final public function sort(mixed $fields = null) { + if(is_string($fields)) { + $fields = explode(',', $fields); + } else if($fields == null) { + $fields = $this->sort; + } + + if(is_array($fields)) { + foreach ($fields as $orderByField) { + $direction = 'asc'; + $directionChar = substr($orderByField, 0, 1); + + if(in_array($directionChar, ['-', '+'])) { + $orderByField = substr($orderByField, 1); + if($directionChar == '-') { + $direction = 'desc'; + } + } + + $this->query->orderBy(trim($orderByField), $direction); + } + } else { + throw new \InvalidArgumentException('Expected string or array, got ' . gettype($fields)); + } + } + + final public function filter(array $filters) { + foreach ($filters as $param => $value) { + $this->filterField($this->query, $param, $value); + } + } + + final public function paginate(int $page = 1, int $limit = -1) { + // Limit + if($limit < 1) { + $limit = $this->limit; + } else { + $limit = min($limit, $this->maxLimit); + } + $this->query->limit($limit); + + // Page + if($page < 1) { + $page = 1; + } + $this->query->offset(($page - 1) * $limit); + } + + final public function includes(Model $model, array $includes) { + foreach($includes as $include) { + $includeMethodName = 'include' . Str::studly($include); + if (method_exists($this, $includeMethodName)) { + $attributeName = Str::snake($include); + $attributeValue = $this->{$includeMethodName}($model); + if($attributeValue !== null) { + $model->$attributeName = $this->{$includeMethodName}($model); + } + } + } + } + + final public function limitFields(array $fields) { + if(empty($fields) !== true) { + $this->query->select($fields); + } + } + + /** overrides */ + public function scope(Builder $builder) { + + } + + public function fields(Model $model) { + $visibleFields = $model->getVisible(); + if (empty($visibleFields)) { + $tableColumns = $model->getConnection() + ->getSchemaBuilder() + ->getColumnListing($model->getTable()); + return $tableColumns; + } + + return $visibleFields; + } + + public function transform(Model $model) { + return $model->toArray(); + } + + public static function viewable(Model $model) { + return true; + } + + public static function creatable() { + return true; + } + + public static function updatable(Model $model) { + return true; + } + + public static function destroyable(Model $model) { + return true; + } +} \ No newline at end of file diff --git a/app/Conductors/EventConductor.php b/app/Conductors/EventConductor.php new file mode 100644 index 0000000..68bc48c --- /dev/null +++ b/app/Conductors/EventConductor.php @@ -0,0 +1,37 @@ +location == 'online') { + unset($model['address']); + } + + return $model->toArray(); + } + + public static function viewable(Model $model) { + return true; + } + + public function includeYaw(Model $model) { + $model->yaw = 'YAW!!'; + } +} \ No newline at end of file diff --git a/app/Conductors/UserConductor.php b/app/Conductors/UserConductor.php new file mode 100644 index 0000000..c05bdbc --- /dev/null +++ b/app/Conductors/UserConductor.php @@ -0,0 +1,53 @@ +user(); + + if($user === null || $user->hasPermission('admin/users') === false) { + return ['id', 'username']; + } + + return parent::fields($model); + } + + public function transform(Model $model) { + $user = auth()->user(); + $data = $model->toArray(); + + if($user === null || strcasecmp($user->id, $model->id) !== 0) { + $fields = ['id', 'username']; + $data = array_intersect_key($data, array_flip($fields)); + } + + return $data; + } + + public static function viewable(Model $model) { + return true; + } + + public static function updatable(Model $model) { + $user = auth()->user(); + + if($user !== null) { + return $user->hasPermission('admin/users') === true || strcasecmp($user->id, $model->id) === 0; + } + + return false; + } + + public static function destroyable(Model $model) { + $user = auth()->user(); + return $user !== null && $user->hasPermission('admin/users') === true; + } +} \ No newline at end of file