public function testNoSpecificColumnNamesSubclassDataObjectQuery() { // This queries all columns from base table and subtable $playerList = new DataList('DataObjectTest_SubTeam'); // Should be a left join. $this->assertEquals(1, preg_match($this->normaliseSQL('/SELECT DISTINCT .* LEFT JOIN .* /'), $this->normaliseSQL($playerList->sql($parameters)))); }
public function scaffoldFormField($title = null, $params = null) { if (empty($this->object)) { return null; } $relationName = substr($this->name, 0, -2); $hasOneClass = DataObject::getSchema()->hasOneComponent(get_class($this->object), $relationName); if (empty($hasOneClass)) { return null; } $hasOneSingleton = singleton($hasOneClass); if ($hasOneSingleton instanceof File) { $field = new UploadField($relationName, $title); if ($hasOneSingleton instanceof Image) { $field->setAllowedFileCategories('image/supported'); } return $field; } // Build selector / numeric field $titleField = $hasOneSingleton->hasField('Title') ? "Title" : "Name"; $list = DataList::create($hasOneClass); // Don't scaffold a dropdown for large tables, as making the list concrete // might exceed the available PHP memory in creating too many DataObject instances if ($list->count() < 100) { $field = new DropdownField($this->name, $title, $list->map('ID', $titleField)); $field->setEmptyString(' '); } else { $field = new NumericField($this->name, $title); } return $field; }
public function getQueryParams() { $params = parent::getQueryParams(); // Remove `Foreign.` query parameters for created objects, // as this would interfere with relations on those objects. foreach (array_keys($params) as $key) { if (stripos($key, 'Foreign.') === 0) { unset($params[$key]); } } return $params; }
/** * Return all objects matching the filter * sub-classes are automatically selected and included * * @param string $callerClass The class of objects to be returned * @param string|array $filter A filter to be inserted into the WHERE clause. * Supports parameterised queries. See SQLSelect::addWhere() for syntax examples. * @param string|array $sort A sort expression to be inserted into the ORDER * BY clause. If omitted, self::$default_sort will be used. * @param string $join Deprecated 3.0 Join clause. Use leftJoin($table, $joinClause) instead. * @param string|array $limit A limit expression to be inserted into the LIMIT clause. * @param string $containerClass The container class to return the results in. * * @todo $containerClass is Ignored, why? * * @return DataList The objects matching the filter, in the class specified by $containerClass */ public static function get($callerClass = null, $filter = "", $sort = "", $join = "", $limit = null, $containerClass = DataList::class) { if ($callerClass == null) { $callerClass = get_called_class(); if ($callerClass == self::class) { throw new \InvalidArgumentException('Call <classname>::get() instead of DataObject::get()'); } if ($filter || $sort || $join || $limit || $containerClass != DataList::class) { throw new \InvalidArgumentException('If calling <classname>::get() then you shouldn\'t pass any other' . ' arguments'); } $result = DataList::create(get_called_class()); $result->setDataModel(DataModel::inst()); return $result; } if ($join) { throw new \InvalidArgumentException('The $join argument has been removed. Use leftJoin($table, $joinClause) instead.'); } $result = DataList::create($callerClass)->where($filter)->sort($sort); if ($limit && strpos($limit, ',') !== false) { $limitArguments = explode(',', $limit); $result = $result->limit($limitArguments[1], $limitArguments[0]); } elseif ($limit) { $result = $result->limit($limit); } $result->setDataModel(DataModel::inst()); return $result; }
public function testDataClassCaseInsensitive() { $list = DataList::create('dataobjecttest_teamcomment'); $this->assertTrue($list->exists()); }
/** * The core search engine, used by this class and its subclasses to do fun stuff. * Searches both SiteTree and File. * * @param array $classesToSearch * @param string $keywords Keywords as a string. * @param int $start * @param int $pageLength * @param string $sortBy * @param string $extraFilter * @param bool $booleanSearch * @param string $alternativeFileFilter * @param bool $invertedMatch * @return PaginatedList * @throws Exception */ public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "Relevance DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false) { $pageClass = 'SilverStripe\\CMS\\Model\\SiteTree'; $fileClass = 'File'; $pageTable = DataObject::getSchema()->tableName($pageClass); $fileTable = DataObject::getSchema()->tableName($fileClass); if (!class_exists($pageClass)) { throw new Exception('MySQLDatabase->searchEngine() requires "SiteTree" class'); } if (!class_exists($fileClass)) { throw new Exception('MySQLDatabase->searchEngine() requires "File" class'); } $keywords = $this->escapeString($keywords); $htmlEntityKeywords = htmlentities($keywords, ENT_NOQUOTES, 'UTF-8'); $extraFilters = array($pageClass => '', $fileClass => ''); if ($booleanSearch) { $boolean = "IN BOOLEAN MODE"; } if ($extraFilter) { $extraFilters[$pageClass] = " AND {$extraFilter}"; if ($alternativeFileFilter) { $extraFilters[$fileClass] = " AND {$alternativeFileFilter}"; } else { $extraFilters[$fileClass] = $extraFilters[$pageClass]; } } // Always ensure that only pages with ShowInSearch = 1 can be searched $extraFilters[$pageClass] .= " AND ShowInSearch <> 0"; // File.ShowInSearch was added later, keep the database driver backwards compatible // by checking for its existence first $fields = $this->getSchemaManager()->fieldList($fileTable); if (array_key_exists('ShowInSearch', $fields)) { $extraFilters[$fileClass] .= " AND ShowInSearch <> 0"; } $limit = $start . ", " . (int) $pageLength; $notMatch = $invertedMatch ? "NOT " : ""; if ($keywords) { $match[$pageClass] = "\n\t\t\t\tMATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('{$keywords}' {$boolean})\n\t\t\t\t+ MATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('{$htmlEntityKeywords}' {$boolean})\n\t\t\t"; $fileClassSQL = Convert::raw2sql($fileClass); $match[$fileClass] = "MATCH (Name, Title) AGAINST ('{$keywords}' {$boolean}) AND ClassName = '{$fileClassSQL}'"; // We make the relevance search by converting a boolean mode search into a normal one $relevanceKeywords = str_replace(array('*', '+', '-'), '', $keywords); $htmlEntityRelevanceKeywords = str_replace(array('*', '+', '-'), '', $htmlEntityKeywords); $relevance[$pageClass] = "MATCH (Title, MenuTitle, Content, MetaDescription) " . "AGAINST ('{$relevanceKeywords}') " . "+ MATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('{$htmlEntityRelevanceKeywords}')"; $relevance[$fileClass] = "MATCH (Name, Title) AGAINST ('{$relevanceKeywords}')"; } else { $relevance[$pageClass] = $relevance[$fileClass] = 1; $match[$pageClass] = $match[$fileClass] = "1 = 1"; } // Generate initial DataLists and base table names $lists = array(); $baseClasses = array($pageClass => '', $fileClass => ''); foreach ($classesToSearch as $class) { $lists[$class] = DataList::create($class)->where($notMatch . $match[$class] . $extraFilters[$class], ""); $baseClasses[$class] = '"' . $class . '"'; } $charset = Config::inst()->get('SilverStripe\\ORM\\Connect\\MySQLDatabase', 'charset'); // Make column selection lists $select = array($pageClass => array("ClassName", "{$pageTable}.\"ID\"", "ParentID", "Title", "MenuTitle", "URLSegment", "Content", "LastEdited", "Created", "Name" => "_{$charset}''", "Relevance" => $relevance[$pageClass], "CanViewType"), $fileClass => array("ClassName", "{$fileTable}.\"ID\"", "ParentID", "Title", "MenuTitle" => "_{$charset}''", "URLSegment" => "_{$charset}''", "Content" => "_{$charset}''", "LastEdited", "Created", "Name", "Relevance" => $relevance[$fileClass], "CanViewType" => "NULL")); // Process and combine queries $querySQLs = array(); $queryParameters = array(); $totalCount = 0; foreach ($lists as $class => $list) { $table = DataObject::getSchema()->tableName($class); /** @var SQLSelect $query */ $query = $list->dataQuery()->query(); // There's no need to do all that joining $query->setFrom($table); $query->setSelect($select[$class]); $query->setOrderBy(array()); $querySQLs[] = $query->sql($parameters); $queryParameters = array_merge($queryParameters, $parameters); $totalCount += $query->unlimitedRowCount(); } $fullQuery = implode(" UNION ", $querySQLs) . " ORDER BY {$sortBy} LIMIT {$limit}"; // Get records $records = $this->preparedQuery($fullQuery, $queryParameters); $objects = array(); foreach ($records as $record) { $objects[] = new $record['ClassName']($record); } $list = new PaginatedList(new ArrayList($objects)); $list->setPageStart($start); $list->setPageLength($pageLength); $list->setTotalItems($totalCount); // The list has already been limited by the query above $list->setLimitItems(false); return $list; }
/** * Returns a json array of a search results that can be used by for example Jquery.ui.autosuggestion * * @param GridField $gridField * @param HTTPRequest $request * @return string */ public function doSearch($gridField, $request) { $dataClass = $gridField->getModelClass(); $allList = $this->searchList ? $this->searchList : DataList::create($dataClass); $searchFields = $this->getSearchFields() ? $this->getSearchFields() : $this->scaffoldSearchFields($dataClass); if (!$searchFields) { throw new LogicException(sprintf('GridFieldAddExistingAutocompleter: No searchable fields could be found for class "%s"', $dataClass)); } $params = array(); foreach ($searchFields as $searchField) { $name = strpos($searchField, ':') !== FALSE ? $searchField : "{$searchField}:StartsWith"; $params[$name] = $request->getVar('gridfield_relationsearch'); } $results = $allList->subtract($gridField->getList())->filterAny($params)->sort(strtok($searchFields[0], ':'), 'ASC')->limit($this->getResultsLimit()); $json = array(); Config::nest(); SSViewer::config()->update('source_file_comments', false); $viewer = SSViewer::fromString($this->resultsFormat); foreach ($results as $result) { $title = html_entity_decode($viewer->process($result)); $json[] = array('label' => $title, 'value' => $title, 'id' => $result->ID); } Config::unnest(); return Convert::array2json($json); }
/** * Returns a SQL object representing the search context for the given * list of query parameters. * * @param array $searchParams Map of search criteria, mostly taked from $_REQUEST. * If a filter is applied to a relationship in dot notation, * the parameter name should have the dots replaced with double underscores, * for example "Comments__Name" instead of the filter name "Comments.Name". * @param array|bool|string $sort Database column to sort on. * Falls back to {@link DataObject::$default_sort} if not provided. * @param array|bool|string $limit * @param DataList $existingQuery * @return DataList * @throws Exception */ public function getQuery($searchParams, $sort = false, $limit = false, $existingQuery = null) { /** DataList $query */ if ($existingQuery) { if (!$existingQuery instanceof DataList) { throw new InvalidArgumentException("existingQuery must be DataList"); } if ($existingQuery->dataClass() != $this->modelClass) { throw new InvalidArgumentException("existingQuery's dataClass is " . $existingQuery->dataClass() . ", {$this->modelClass} expected."); } $query = $existingQuery; } else { $query = DataList::create($this->modelClass); } if (is_array($limit)) { $query = $query->limit(isset($limit['limit']) ? $limit['limit'] : null, isset($limit['start']) ? $limit['start'] : null); } else { $query = $query->limit($limit); } $query = $query->sort($sort); // hack to work with $searchParems when it's an Object if ($searchParams instanceof HTTPRequest) { $searchParamArray = $searchParams->getVars(); } else { $searchParamArray = $searchParams; } foreach ($searchParamArray as $key => $value) { $key = str_replace('__', '.', $key); if ($filter = $this->getFilter($key)) { $filter->setModel($this->modelClass); $filter->setValue($value); if (!$filter->isEmpty()) { $query = $query->alterDataQuery(array($filter, 'apply')); } } } if ($this->connective != "AND") { throw new Exception("SearchContext connective '{$this->connective}' not supported after ORM-rewrite."); } return $query; }
/** * This allows templates to create a new `DataList` from a known * DataObject class name, and call methods such as aggregates. * * The common use case is for partial caching: * <code> * <% cached List(Member).max(LastEdited) %> * loop members here * <% end_cached %> * </code> * * @param string $className * @return DataList */ public static function getDataList($className) { $list = new DataList($className); $list->setDataModel(DataModel::inst()); return $list; }
/** * @param int $folderID The ID of the folder to display. * @return FormField */ protected function getListField($folderID) { // Generate the folder selection field. /** @skipUpgrade */ $folderField = new TreeDropdownField('ParentID', _t('HTMLEditorField.FOLDER', 'Folder'), 'SilverStripe\\Assets\\Folder'); $folderField->setValue($folderID); // Generate the file list field. $config = GridFieldConfig::create(); $config->addComponent(new GridFieldSortableHeader()); $config->addComponent(new GridFieldFilterHeader()); $config->addComponent($colsComponent = new GridFieldDataColumns()); $colsComponent->setDisplayFields(array('StripThumbnail' => '', 'Title' => File::singleton()->fieldLabel('Title'), 'Created' => File::singleton()->fieldLabel('Created'), 'Size' => File::singleton()->fieldLabel('Size'))); $colsComponent->setFieldCasting(array('Created' => 'DBDatetime->Nice')); // Set configurable pagination for file list field $pageSize = Config::inst()->get(get_class($this), 'page_size'); $config->addComponent(new GridFieldPaginator($pageSize)); // If relation is to be autoset, we need to make sure we only list compatible objects. $baseClass = $this->parent->getRelationAutosetClass(); // Create the data source for the list of files within the current directory. $files = DataList::create($baseClass)->exclude('ClassName', 'SilverStripe\\Assets\\Folder'); if ($folderID) { $files = $files->filter('ParentID', $folderID); } $fileField = new GridField('Files', false, $files, $config); $fileField->setAttribute('data-selectable', true); if ($this->parent->getAllowedMaxFileNumber() !== 1) { $fileField->setAttribute('data-multiselect', true); } $selectComposite = new CompositeField($folderField, $fileField); return $selectComposite; }
/** * Return a list of all versions for a given id. * * @param string $class * @param int $id * * @return DataList */ public static function get_all_versions($class, $id) { $list = DataList::create($class)->filter('ID', $id)->setDataQueryParam('Versioned.mode', 'all_versions'); return $list; }
/** * This method returns a copy of this list that does not contain any DataObjects that exists in $list * * The $list passed needs to contain the same dataclass as $this * * @param DataList $list * @return DataList * @throws BadMethodCallException */ public function subtract(DataList $list) { if ($this->dataClass() != $list->dataClass()) { throw new InvalidArgumentException('The list passed must have the same dataclass as this class'); } return $this->alterDataQuery(function (DataQuery $query) use($list) { $query->subtract($list->dataQuery()); }); }