request = $request; } /** * Only include the specified attributes in the results. * * @param string|array $only Only return these attributes. * @return void */ public function only(mixed $only) { if (is_array($only) === true) { $this->only = $only; } else { $this->only = [$only]; } } /** * Exclude the specified attributes in the results. * * @param string|array $exclude Attributes to exclude. * @return void */ public function exclude(mixed $exclude) { if (is_array($exclude) === true) { $this->exclude = $exclude; } else { $this->exclude = [$exclude]; } } /** * Check if the model is viewable by the user * * @param mixed $model Model instance. * @param mixed $user Current user. * @return boolean */ // protected function viewable(mixed $model, mixed $user) // { // return true; // } /** * Prepend action to the builder to limit the results * * @param Builder $builder Builder instance. * @param mixed $user Current user. * @return Builder|null */ // protected function prebuild(Builder $builder, mixed $user) // { // return $builder; // } /** * Return an array of attributes visible in the results * * @param array $attributes Attributes currently visible. * @param User|null $user Current logged in user or null. * @return mixed */ protected function seeAttributes(array $attributes, mixed $user) { return $attributes; } /** * Apply all the requested filters if available. * * @param Model $model Model object to filter. If null create query. * @return Builder|Model */ public function filter(Model $model = null) { $this->foundTotal = 0; $builder = $this->class::query(); /* Get the related model */ $classModel = $model; if ($model === null) { $classModel = $builder->getModel(); } /* Get table name */ if ($this->table === '') { if ($model === null) { $this->table = $classModel->getTable(); } else { $this->table = $model->getTable(); } } /* Run query prebuilder or viewable */ if ($model === null) { if (method_exists($this, 'prebuild') === true) { $prebuilder = $this->prebuild($builder, $this->request->user()); if ($prebuilder instanceof Builder) { $builder = $prebuilder; } } } else { if (method_exists($this, 'viewable') === true) { if ($this->viewable($model, $this->request->user()) === false) { return null; } } } /* Get attributes from table or use 'only' */ $attributes = []; if (is_array($this->only) === true && count($this->only) > 0) { $attributes = $this->only; } else { $attributes = Schema::getColumnListing($this->table); } /* Run attribute modifiers*/ $modifiedAttribs = $this->seeAttributes($attributes, $this->request->user()); if (is_array($modifiedAttribs) === true) { $attributes = $modifiedAttribs; } foreach ($attributes as $key => $column) { $method = 'see' . Str::studly($column) . 'Attribute'; if ( method_exists($this, $method) === true && $this->$method($this->request->user()) === false ) { unset($attributes[$key]); } } if (is_array($this->exclude) === true && count($this->exclude) > 0) { $attributes = array_diff($attributes, $this->exclude); } /* Setup attributes and appends */ // $attributesAppends = array_merge($attributes, $classModel->getAppends()); /* Apply ?fields= request to attributes */ if ($this->request->has('fields') === true) { $attributes = array_intersect($attributes, explode(',', $this->request->fields)); } /* Hide remaining attributes in model (if present) and return */ if ($model !== null) { // TODO: Also show $this->request->fields that are appends $model->makeHidden(array_diff(Schema::getColumnListing($this->table), $attributes)); return $model; } /* Are there attributes left? */ if (count($attributes) === 0) { $this->foundTotal = 0; return new Collection(); } /* apply select! */ $builder->select($attributes); /* Setup filterables if not present */ if ($this->filterable === null) { $this->filterable = $attributes; } /* Filter values */ $filterRequest = array_filter($this->request->only(array_intersect($attributes, $this->filterable))); $this->builderArrayFilter($builder, $filterRequest); if (is_array($this->q) === true && count($this->q) > 0) { $qQueries = []; foreach ($this->q as $key => $value) { if (is_array($value) === true) { $qKey = $key === '_' ? '' : $key; foreach ($value as $subvalue) { $qQueries[$key][$subvalue] = $this->request->get("q" . $qKey); } } elseif ($this->request->has("q") === true) { $qQueries['_'][$value] = $this->request->get("q"); } } foreach ($qQueries as $key => $value) { $builder->where(function ($query) use ($value) { $this->builderArrayFilter($query, $value, 'or'); }); } }//end if /* Apply sorting */ $sortList = $this->defaultSort; if ($this->request->has('sort') === true) { $sortList = explode(',', $this->request->sort); } /* Transform sort list to array */ if (is_array($sortList) === false) { if (strlen($sortList) > 0) { $sortList = [$sortList]; } else { $sortList = []; } } /* Remove non-viewable attributes from sort list */ if (count($sortList) > 0) { $sortList = array_filter($sortList, function ($item) use ($attributes) { $parsedItem = $item; if (substr($parsedItem, 0, 1) === '-') { $parsedItem = substr($parsedItem, 1); } return in_array($parsedItem, $attributes); }); } /* Do we have any sort element left? */ if (count($sortList) > 0) { foreach ($sortList as $sortAttribute) { $prefix = substr($sortAttribute, 0, 1); $direction = 'asc'; if (in_array($prefix, ['-', '+']) === true) { $sortAttribute = substr($sortAttribute, 1); if ($prefix === '-') { $direction = 'desc'; } } $builder->orderBy($sortAttribute, $direction); }//end foreach }//end if /* save found count */ $this->foundTotal = $builder->count(); /* Apply result limit */ $limit = $this->defaultLimit; if ($this->request->has('limit') === true) { $limit = intval($this->request->limit); } if ($limit < 1) { $limit = 1; } if ($limit > $this->maxLimit && $this->maxLimit !== 0) { $limit = $this->maxLimit; } $builder->limit($limit); /* Apply page offset */ if ($this->request->has('page') === true) { $page = intval($this->request->page); if ($page < 1) { $page = 1; } $builder->offset((intval($this->request->page) - 1) * $limit); } /* run spot run */ $collection = $builder->get(); return $collection; } /** * Filter content based on the filterRequest * @param mixed $builder Builder object * @param array $filterRequest Filter key/value * @param string $defaultBoolean Default where boolean * @return void */ protected function builderArrayFilter(mixed $builder, array $filterRequest, string $defaultBoolean = 'and') { foreach ($filterRequest as $filterAttribute => $filterValue) { $tags = []; $boolean = $defaultBoolean; $matches = preg_split('/(? $match_info) { if (($idx % 2) === true) { if (substr($filterValue, ($match_info[1] - 2), 1) === ',') { $tags[] = ['operator' => '', 'tag' => stripslashes(trim($match_info[0]))]; } else { $tags[(count($tags) - 1)]['tag'] .= stripslashes(trim($match_info[0])); } } else { $innerTags = [$match_info[0]]; if (strpos($match_info[0], ',') !== false) { $innerTags = preg_split('/(? 0) { $operator = '='; $single = substr($tag, 0, 1); $double = substr($tag . ' ', 0, 2); // add empty space incase len $tag < 2 // check for operators at start if (in_array($double, ['!=', '<>', '><', '>=', '<=', '=>', '=<']) === true) { if ($double === '<>' || $double === '><') { $double = '!='; } elseif ($double === '=>') { $double = '>='; } elseif ($double === '=<') { $double == '>='; } $operator = $double; $tag = substr($tag, 2); } else { if (in_array($single, ['=', '!', '>', '<', '~', '%']) === true) { if ($single === '=') { $single = '=='; // a single '=' is actually a double '==' } $operator = $single; $tag = substr($tag, 1); } }//end if $tags[] = ['operator' => $operator, 'tag' => $tag]; }//end if }//end foreach }//end if }//end foreach if (count($tags) > 1) { $boolean = 'or'; } foreach ($tags as $tag_data) { $operator = $tag_data['operator']; $value = $tag_data['tag']; $table = $this->table; $column = $filterAttribute; if (($dotPos = strpos($filterAttribute, '.')) !== false) { $table = substr($filterAttribute, 0, $dotPos); $column = substr($filterAttribute, ($dotPos + 1)); } $columnType = DB::getSchemaBuilder()->getColumnType($table, $column); if ( in_array($columnType, ['tinyint', 'smallint', 'mediumint', 'int', 'integer', 'bigint', 'decimal', 'float', 'double', 'real', 'double precision' ]) === true ) { if (in_array($operator, ['=', '>', '<', '>=', '<=', '%', '!']) === false) { continue; } $columnType = 'numeric'; } elseif (in_array($columnType, ['date', 'time', 'datetime', 'timestamp', 'year']) === true) { if (in_array($operator, ['=', '>', '<', '>=', '<=', '!']) === false) { continue; } $columnType = 'datetime'; } elseif ( in_array($columnType, ['string', 'char', 'varchar', 'timeblob', 'blob', 'mediumblob', 'longblob', 'tinytext', 'text', 'mediumtext', 'longtext', 'enum' ]) === true ) { if (in_array($operator, ['=', '==', '!', '!=', '~']) === false) { continue; } $columnType = 'text'; if ($value === "''" || $value === '""') { $value = ''; } elseif (strcasecmp($value, 'null') !== 0) { if ($operator === '!') { $operator = 'NOT LIKE'; $value = '%' . $value . '%'; } elseif ($operator === '=') { $operator = 'LIKE'; $value = '%' . $value . '%'; } elseif ($operator === '~') { $operator = 'SOUNDS LIKE'; } elseif ($operator === '==') { $operator = '='; } } } elseif ($columnType === 'boolean') { if (in_array($operator, ['=', '!']) === false) { continue; } if (strtolower($value) === 'true') { $value = 1; } elseif (strtolower($value) === 'false') { $value = 0; } }//end if $betweenSeperator = strpos($value, '<>'); if ( $operator === '=' && $betweenSeperator !== false && in_array($columnType, ['numeric', 'datetime' ]) === true ) { $value = explode('<>', $value); $operator = '<>'; } if ($operator !== '') { $this->builderWhere($builder, $table, $column, $operator, $value, $boolean); } }//end foreach }//end foreach } /** * Insert a where statement into the builder, taking the filter map into consideration * * @param Builder $builder Builder instance. * @param string $table Table name. * @param string $column Column name. * @param string $operator Where operator. * @param mixed $value Value to test. * @param string $boolean Use Or comparison. * @return void * @throws RuntimeException Error applying statement. * @throws InvalidArgumentException Error applying statement. */ protected function builderWhere( Builder &$builder, string $table, string $column, string $operator, mixed $value, string $boolean ) { if ( (is_string($value) === true && $operator !== '<>') || (is_array($value) === true && count($value) === 2 && $operator === '<>') ) { if ($table !== '' && $table !== $this->table) { $builder->whereHas($table, function ($query) use ($column, $operator, $value, $boolean) { if ($operator !== '<>') { if (strcasecmp($value, 'null') === 0) { if ($operator === '!') { $query->whereNotNull($column, $boolean); } else { $query->whereNull($column, $boolean); } } else { $query->where($column, $operator, $value, $boolean); } } else { $query->whereBetween($column, $value, $boolean); } }); } else { if ($operator !== '<>') { if (strcasecmp($value, 'null') === 0) { if ($operator === '!') { $builder->whereNotNull($column, $boolean); } else { $builder->whereNull($column, $boolean); } } else { $builder->where($column, $operator, $value, $boolean); } } else { $builder->whereBetween($column, $value, $boolean); } }//end if }//end if } /** * Return the found total of items * @return integer */ public function foundTotal() { return $this->foundTotal; } }