/** * Displays a list of all members on the site that belong to the selected * groups. * * @return string */ public function handleList($request) { $fields = $this->parent->Fields()->filter('MemberListVisible', true); $members = $this->parent->Groups()->relation('Members'); $members = new PaginatedList($members, $request); $list = new PaginatedList(new ArrayList(), $request); $list->setLimitItems(false); $list->setTotalItems($members->getTotalItems()); foreach ($members as $member) { $cols = new ArrayList(); $public = $member->getPublicFields(); $link = $this->Link($member->ID); foreach ($fields as $field) { if ($field->PublicVisibility == 'MemberChoice' && !in_array($field->MemberField, $public)) { $value = null; } else { $value = $member->{$field->MemberField}; } $cols->push(new ArrayData(array('Name' => $field->MemberField, 'Title' => $field->Title, 'Value' => $value, 'Sortable' => $member->hasDatabaseField($field->MemberField), 'Link' => $link))); } $list->push($member->customise(array('Fields' => $cols))); } $this->data()->Title = _t('MemberProfiles.MEMBERLIST', 'Member List'); $this->data()->Parent = $this->parent; $controller = $this->customise(array('Members' => $list)); return $controller->renderWith(array('MemberProfileViewer_list', 'MemberProfileViewer', 'Page')); }
/** * @param string $type future, all, past * @return PaginatedList */ public function getPaginatedItems() { $items = $this->getItems(); $paginatedList = new PaginatedList($items, $this->request); $paginatedList->setPageLength($this->stat('page_length')); $paginatedList->setLimitItems(true); return $paginatedList; }
public function testLimitItems() { $list = new ArrayList(range(1, 50)); $list = new PaginatedList($list); $list->setCurrentPage(3); $this->assertEquals(10, count($list->getIterator()->getInnerIterator())); $list->setLimitItems(false); $this->assertEquals(50, count($list->getIterator()->getInnerIterator())); }
/** * The core search engine, used by this class and its subclasses to do fun stuff. * Searches both SiteTree and File. * * @param string $keywords Keywords as a string. */ public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "Relevance DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false) { if (!class_exists('SiteTree')) { throw new Exception('MySQLDatabase->searchEngine() requires "SiteTree" class'); } if (!class_exists('File')) { throw new Exception('MySQLDatabase->searchEngine() requires "File" class'); } $fileFilter = ''; $keywords = Convert::raw2sql($keywords); $htmlEntityKeywords = htmlentities($keywords, ENT_NOQUOTES, 'UTF-8'); $extraFilters = array('SiteTree' => '', 'File' => ''); if ($booleanSearch) { $boolean = "IN BOOLEAN MODE"; } if ($extraFilter) { $extraFilters['SiteTree'] = " AND {$extraFilter}"; if ($alternativeFileFilter) { $extraFilters['File'] = " AND {$alternativeFileFilter}"; } else { $extraFilters['File'] = $extraFilters['SiteTree']; } } // Always ensure that only pages with ShowInSearch = 1 can be searched $extraFilters['SiteTree'] .= " AND ShowInSearch <> 0"; // File.ShowInSearch was added later, keep the database driver backwards compatible // by checking for its existence first $fields = $this->fieldList('File'); if (array_key_exists('ShowInSearch', $fields)) { $extraFilters['File'] .= " AND ShowInSearch <> 0"; } $limit = $start . ", " . (int) $pageLength; $notMatch = $invertedMatch ? "NOT " : ""; if ($keywords) { $match['SiteTree'] = "\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"; $match['File'] = "MATCH (Filename, Title, Content) AGAINST ('{$keywords}' {$boolean}) AND ClassName = 'File'"; // 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['SiteTree'] = "MATCH (Title, MenuTitle, Content, MetaDescription) " . "AGAINST ('{$relevanceKeywords}') " . "+ MATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('{$htmlEntityRelevanceKeywords}')"; $relevance['File'] = "MATCH (Filename, Title, Content) AGAINST ('{$relevanceKeywords}')"; } else { $relevance['SiteTree'] = $relevance['File'] = 1; $match['SiteTree'] = $match['File'] = "1 = 1"; } // Generate initial DataLists and base table names $lists = array(); $baseClasses = array('SiteTree' => '', 'File' => ''); foreach ($classesToSearch as $class) { $lists[$class] = DataList::create($class)->where($notMatch . $match[$class] . $extraFilters[$class], ""); $baseClasses[$class] = '"' . $class . '"'; } // Make column selection lists $select = array('SiteTree' => array("ClassName", "{$baseClasses['SiteTree']}.\"ID\"", "ParentID", "Title", "MenuTitle", "URLSegment", "Content", "LastEdited", "Created", "Filename" => "_utf8''", "Name" => "_utf8''", "Relevance" => $relevance['SiteTree'], "CanViewType"), 'File' => array("ClassName", "{$baseClasses['File']}.\"ID\"", "ParentID" => "_utf8''", "Title", "MenuTitle" => "_utf8''", "URLSegment" => "_utf8''", "Content", "LastEdited", "Created", "Filename", "Name", "Relevance" => $relevance['File'], "CanViewType" => "NULL")); // Process and combine queries $querySQLs = array(); $totalCount = 0; foreach ($lists as $class => $list) { $query = $list->dataQuery()->query(); // There's no need to do all that joining $query->setFrom(array(str_replace(array('"', '`'), '', $baseClasses[$class]) => $baseClasses[$class])); $query->setSelect($select[$class]); $query->setOrderBy(array()); $querySQLs[] = $query->sql(); $totalCount += $query->unlimitedRowCount(); } $fullQuery = implode(" UNION ", $querySQLs) . " ORDER BY {$sortBy} LIMIT {$limit}"; // Get records $records = DB::query($fullQuery); $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; }
/** * Perform a search against the data table. * * @param array $where Array of strings to add into the WHERE clause * @param array $orderby Array of column as key, to direction as value to add into the ORDER BY clause * @param string|int $start Record to start at (for paging) * @param string|int $pageLength Number of results per page (for paging) * @param boolean $paged Paged results or not? * @return ArrayList|PaginatedList */ protected function queryList($where = array(), $orderby = array(), $start, $pageLength, $paged = true) { $dataClass = $this->dataRecord->getDataClass(); if (!$dataClass) { return new PaginatedList(new ArrayList()); } $resultColumns = $this->dataRecord->getDataSingleton()->summaryFields(); $resultColumns['ID'] = 'ID'; $results = new ArrayList(); $query = new SQLQuery(); $query->setSelect($this->escapeSelect(array_keys($resultColumns)))->setFrom("\"{$dataClass}\""); $query->addWhere($where); $query->addOrderBy($orderby); $query->setConnective('AND'); if ($paged) { $query->setLimit($pageLength, $start); } foreach ($query->execute() as $record) { $result = new $dataClass($record); $result->Columns = $this->Columns($result); // we attach Columns here so the template can loop through them on each result $results->push($result); } if ($paged) { $list = new PaginatedList($results); $list->setPageStart($start); $list->setPageLength($pageLength); $list->setTotalItems($query->unlimitedRowCount()); $list->setLimitItems(false); } else { $list = $results; } return $list; }
/** * Gets the results from the given query. * * Appends to the usual results the matches on the GtkdocSection * instances on the following order: * * 1. GtkdocSections with matches in 'Title' * 2. GtkdocSections with matches in 'MetaDescription' * 3. GtkdocSections with matches in 'Content' * * Eventual duplicates are dropped from the list. * * @param array $data The raw data submitted by user * @param SearchForm $form The form instance * @param SS_HTTPRequest $request Request for this action */ public function results($data, $form, $request) { $keywords = $data['Search']; // Populate the first matches in the usual way $matches = $form->getResults()->toArray(); // Get the list of GtkdocSection with matching title... $context = singleton('GtkdocSection')->getDefaultSearchContext(); $gtkdoc_matches = $context->getResults(array('Title' => $keywords))->toArray(); // ...and add to them the list of GtkdocSection with matching // description and matching content, skipping the duplicates... self::merge($gtkdoc_matches, $context->getResults(array('MetaDescription' => $keywords))->toArray()); self::merge($gtkdoc_matches, $context->getResults(array('Content' => $keywords))->toArray()); // Append the GtkdocSection matches to the original ones $matches = array_merge($matches, $gtkdoc_matches); $results = new PaginatedList(new ArrayList($matches)); $results->setPageLength(10); $results->setLimitItems(true); $results->setPageStart($request->getVar('start')); $data = array('Results' => $results, 'Query' => $form->getSearchQuery(), 'Title' => _t('SearchForm.SearchResults', 'Search Results')); return $this->owner->customise($data)->renderWith(array('Page_results', 'Page')); }
/** * The core search engine configuration. * @todo Properly extract the search functions out of the core. * * @param string $keywords Keywords as a space separated string * @return object DataObjectSet of result pages */ public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "ts_rank DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false) { //Fix the keywords to be ts_query compatitble: //Spaces must have pipes //@TODO: properly handle boolean operators here. $keywords = trim($keywords); $keywords = str_replace(' ', ' | ', $keywords); $keywords = str_replace('"', "'", $keywords); $keywords = $this->quoteString(trim($keywords)); //We can get a list of all the tsvector columns though this query: //We know what tables to search in based on the $classesToSearch variable: $classesPlaceholders = DB::placeholders($classesToSearch); $result = $this->preparedQuery("\n\t\t\tSELECT table_name, column_name, data_type\n\t\t\tFROM information_schema.columns\n\t\t\tWHERE data_type='tsvector' AND table_name in ({$classesPlaceholders});", $classesToSearch); if (!$result->numRecords()) { throw new Exception('there are no full text columns to search'); } $tables = array(); $tableParameters = array(); // Make column selection lists $select = array('SiteTree' => array('"ClassName"', '"SiteTree"."ID"', '"ParentID"', '"Title"', '"URLSegment"', '"Content"', '"LastEdited"', '"Created"', 'NULL AS "Name"', '"CanViewType"'), 'File' => array('"ClassName"', '"File"."ID"', '0 AS "ParentID"', '"Title"', 'NULL AS "URLSegment"', 'NULL AS "Content"', '"LastEdited"', '"Created"', '"Name"', 'NULL AS "CanViewType"')); foreach ($result as $row) { $conditions = array(); if ($row['table_name'] === 'SiteTree' || $row['table_name'] === 'File') { $conditions[] = array('"ShowInSearch"' => 1); } $method = self::default_fts_search_method(); $conditions[] = "\"{$row['table_name']}\".\"{$row['column_name']}\" {$method} q "; $query = DataObject::get($row['table_name'], $conditions)->dataQuery()->query(); // Could parameterise this, but convention is only to to so for where conditions $query->addFrom(array('tsearch' => ", to_tsquery('" . self::search_language() . "', {$keywords}) AS q")); $query->setSelect(array()); foreach ($select[$row['table_name']] as $clause) { if (preg_match('/^(.*) +AS +"?([^"]*)"?/i', $clause, $matches)) { $query->selectField($matches[1], $matches[2]); } else { $query->selectField($clause); } } $query->selectField("ts_rank(\"{$row['table_name']}\".\"{$row['column_name']}\", q)", 'Relevance'); $query->setOrderBy(array()); //Add this query to the collection $tables[] = $query->sql($parameters); $tableParameters = array_merge($tableParameters, $parameters); } $limit = $pageLength; $offset = $start; if ($keywords) { $orderBy = " ORDER BY {$sortBy}"; } else { $orderBy = ''; } $fullQuery = "SELECT * FROM (" . implode(" UNION ", $tables) . ") AS q1 {$orderBy} LIMIT {$limit} OFFSET {$offset}"; // Get records $records = $this->preparedQuery($fullQuery, $tableParameters); $totalCount = 0; foreach ($records as $record) { $objects[] = new $record['ClassName']($record); $totalCount++; } if (isset($objects)) { $results = new ArrayList($objects); } else { $results = new ArrayList(); } $list = new PaginatedList($results); $list->setLimitItems(false); $list->setPageStart($start); $list->setPageLength($pageLength); $list->setTotalItems($totalCount); return $list; }