public function search(NodeQuery $dto) { $searchSafe = $dto->getParameter('SearchKeywords'); $searchRaw = $dto->getParameter('SearchKeywordsRaw') == null ? preg_replace("/[\\(\\)\\/\\*\\[\\]\\?]+/", '', $searchSafe) : $dto->getParameter('SearchKeywordsRaw'); $searchThreshold = $dto->getParameter('SearchThreshold'); $maxresults = $dto->getParameter('SearchMaxResults') != null ? $dto->getParameter('SearchMaxResults') : 500; $sortCol = $dto->getParameter('SearchSort') != null ? $dto->getParameter('SearchSort') : 'relevance'; $sortDir = $dto->getParameter('SearchSortDirection') != null ? $dto->getParameter('SearchSortDirection') : 'desc'; // $stopwords = array ('/\bof\b/','/\ba\b/','/\band\b/','/\bthe\b/'); // $s = str_replace('+','\+',$searchRaw); // $s = preg_replace($stopwords,' ',$s); //$s = preg_replace("/\s+/",' ',$s); $s = preg_replace("/\\s+/", ' ', $searchRaw); // execute query $rows = $this->executeIndexQuery($this->getReadConnection(), $dto, $s, $searchSafe, $maxresults); if (sizeof($rows) == 0) { return $dto; } $datascore = $this->columnScores(); $words = StringUtils::wordsTokenized($s); $scores = array(); $sorts = array(); $resultingNodeRefs = array(); $preg_s = preg_quote(str_replace(array('*', '+', '-'), '', $s), '/'); $ct = 0; foreach ($rows as $row) { $negative_match = FALSE; $found = array(); if ($ct++ > $maxresults) { break; } $score = $row['Score']; if (preg_match("/^{$preg_s}/i", $row['Title'])) { $score += 10; } elseif (sizeof($words) == 1 && preg_match("/\\b" . $preg_s . "/i", $row['Title'])) { $score += 5; } foreach ($datascore as $param => $val) { if (!empty($s) && !empty($row[$param]) && preg_match("/\\b" . $preg_s . "\\b/si", " " . $row[$param] . " ")) { $score += $val; } $m[$param] = 0; foreach ($words as $word) { if (preg_match("/^-(.{2,})/", $word, $m) && preg_match("/\\b{$m['1']}\\b/is", $row[$param])) { $negative_match = TRUE; } $preg_word = preg_quote($word, '/'); if (!empty($word) && preg_match("/\\b{$preg_word}\\b/si", " " . $row[$param] . " ")) { if (!isset($m[$param])) { $m[$param] = 0; } $m[$param]++; $found[$word] = 1; $score += $val / 2; } } if (isset($m[$param]) && $m[$param] == sizeof($words)) { $score += $val * 3; } } if (sizeof($found) > 0) { $score = $score * (sizeof($found) / sizeof($words)); } $rowElement = $this->ElementService->getByID($row['ElementID']); $rowSlug = $row['Slug']; $rowNodeRef = new NodeRef($rowElement, $rowSlug); $rawscores['' . $rowNodeRef] = $score; if (!$negative_match) { if (empty($searchThreshold) || $score > $searchThreshold) { $resultingNodeRefs['' . $rowNodeRef] = $rowNodeRef; $scores['' . $rowNodeRef] = $score; $sorts['' . $rowNodeRef] = $sortCol != 'relevance' ? $row[$sortCol] : $score; } } } reset($scores); if (sizeof($sorts) == 0) { return $dto; } if (strtolower($sortDir) == 'asc') { asort($sorts); } else { arsort($sorts); } // $this->Logger->debug($resultingNodeRefs); // $this->Logger->debug($sorts); $results = ArrayUtils::arraySortUsingKeys($resultingNodeRefs, array_keys($sorts)); $dto->setParameter('NodeRefs.in', $results); $dto->setParameter('NodeRefs.fullyQualified', true); $dto->setOrderBy('NodeRefs'); $results = $this->NodeService->findAll($dto)->getResults(); foreach ($results as $key => &$node) { $nodeRef = $node['NodeRef']; $node['SearchScore'] = $scores['' . $nodeRef]; // error_log($node['Title'].' ('.$node['SearchScore'].')'); } // $keys = array_map('strval', $sorts); $dto->setResults($results); return $dto; }
/** * @throws NodeException * @param NodeQuery $nodeQuery * @param bool $forceReadWrite * @return NodeQuery */ public function findAll(NodeQuery $nodeQuery, $forceReadWrite = false) { $this->Benchmark->start('findall'); // $this->Logger->debug($nodeQuery); $this->Events->trigger('Node.findAll', $nodeQuery); if ($nodeQuery->getResults() !== null) { return $nodeQuery; } // NODEREFS //list($nodeRefs, $nodePartials, $allFullyQualified) = $this->NodeRefService->parseFromNodeQuery($nodeQuery); $this->NodeRefService->normalizeNodeQuery($nodeQuery); $nodeRefs = $nodeQuery->getParameter('NodeRefs.normalized'); $nodePartials = $nodeQuery->getParameter('NodePartials.eq'); $allFullyQualified = $nodeQuery->getParameter('NodeRefs.fullyQualified'); if (empty($nodeRefs)) { return $nodeQuery; } // foreach((array)$nodeRefs as $k => $nodeRef) // $this->NodeEvents->fireNodeEvents('find', '', $nodeRef, $nodePartials, $nodeQuery); $orderObjects = $this->NodesHelper->getOrderObjects($nodeQuery); // $this->currentOrderingObjects = $orderObjects; $co = false; if ($nodeQuery->hasParameter('Count.only') && ($co = $nodeQuery->getParameter('Count.only')) != true) { throw new NodeException('Count.only parameter must be equal to true'); } $checkPermissions = $nodeQuery->hasParameter('Permissions.check') && $nodeQuery->getParameter('Permissions.check') == true; $doCounts = $nodeQuery->isRetrieveTotalRecords() || $co; $offset = $nodeQuery->getOffset() != null ? $nodeQuery->getOffset() : 0; $limit = $nodeQuery->getLimit(); $allowDeleted = $nodeQuery->getParameter('Status.all') != null || $nodeQuery->getParameter('Status.eq') == 'deleted'; if ($allFullyQualified) { $this->Logger->debug('RETRIEVING, SORTING, AND FILTERING IN PHP'); // TODO: limit this to 50 nodes, PHP can't handle more $existsNodePartials = $this->NodesHelper->createNodePartialsFromExistsClauses($nodeQuery); // RETRIEVAL PHASE $resultingRows = $this->NodeMultiGetDAO->multiGet($nodeRefs, $existsNodePartials, $forceReadWrite, $checkPermissions, $allowDeleted); // FILTER PHASE $resultingRows = $this->NodesHelper->filterNodes($resultingRows, $nodeQuery); if ($doCounts) { $totalCount = count($resultingRows); } if (!empty($resultingRows)) { // SORTING PHASE $resultingRows = $this->NodesHelper->sortNodes($resultingRows, $orderObjects, true); // LIMIT/OFFSET PHASE $resultingRows = $this->NodesHelper->sliceNodes($resultingRows, $limit, $offset); } } else { $this->Logger->debug('RETRIEVING, SORTING, AND FILTERING IN MySQL'); $moreThan1Table = false; $connectionCouplets = $this->getResolvedConnectionCouplets($nodeRefs, $forceReadWrite); $resultingRows = array(); $queries = array(); $elements = array(); $c = 0; $totalCount = 0; //$query = null; //$firstTable = null; //$firstTableID = null; foreach ($connectionCouplets as $connectionCouplet) { $db = $connectionCouplet->getConnection(); $tableToSlugs = $connectionCouplet->getAttribute('tablesToSlugs'); //if(is_null($limit) && count($tableToSlugs) > 1) // throw new Exception('Cannot have limitless query across multiple tables'); foreach ($tableToSlugs as $table => $tableInfo) { extract($tableInfo); //if(!$query) $query = $this->buildMySQLQuery($db, $tableNodeRef, $table, $tableid, $nodeQuery, $orderObjects, $slugs); //if(!$firstTable) // $firstTable = $table; //if(!$firstTableID) // $firstTableID = $tableid; $elementid = $element->getElementID(); $elements[$elementid] = $element; $queries[] = array('query' => $query, 'elementid' => $elementid, 'tableid' => $tableid, 'table' => $table, 'db' => $db); $tableInfo = null; } $tableToSlugs = null; $connectionCouplet = null; } $connectionCouplets = null; $moreThan1Table = count($queries) > 1; $qcount = 1; foreach ($queries as $qArgs) { extract($qArgs); $q = clone $query; if ($doCounts) { $cq = clone $q; $cq->clearSelect(); $cq->ORDERBY(null); //$cq->select("COUNT({$db->quoteIdentifier($firstTable)}.$firstTableID)"); $cq->select("COUNT(DISTINCT {$db->quoteIdentifier($table)}.{$tableid})"); $s = (string) $cq; //$s = str_replace($firstTable, $table, $s); //$s = str_replace($firstTableID, $tableid, $s); $totalCount += (int) $db->readField($s); if ($co) { // Counts.only continue; } } $batchOffset = $moreThan1Table ? 0 : $offset; $batchLimit = $moreThan1Table ? $this->nodeDatabaseBatchLimit : $limit; while ($batchOffset > -1) { $rows = null; $reorderOnce = false; $q->LIMIT($moreThan1Table && $batchLimit > 1 ? $batchLimit + 1 : $batchLimit); $q->OFFSET($batchOffset); $s = (string) $q; //$s = str_replace($firstTable, $table, $s); //$s = str_replace($firstTableID, $tableid, $s); $rows = $db->readAll($s); if (empty($rows)) { $batchOffset = -1; break; } // $this->Benchmark->start('pushrows'); foreach ($rows as $k => $row) { if ($batchLimit > 1 && $k > $batchLimit - 1) { break; } $row['ElementID'] = $elementid; // if there is no limit, push all rows // if there is only 1 table to aggregate, push all rows // if the index of the current result set is less than the total needed rows, push if (is_null($limit) || !$moreThan1Table || $c < $limit + $offset) { $row['NodeRef'] = new NodeRef($elements[$row['ElementID']], $row['Slug']); $resultingRows[] = $row; ++$c; } else { if ($k >= $limit + $offset) { // done with this table (element) $batchOffset = -1; break 2; } if (!$reorderOnce) { $reorderOnce = true; $resultingRows = $this->NodesHelper->sortNodes($resultingRows, $orderObjects); } $lastRow = $resultingRows[$c - 1]; // if this row is before the last row in the order, add it in if (!$this->NodesHelper->compareNodes($lastRow, $row, $orderObjects)) { $row['NodeRef'] = new NodeRef($elements[$row['ElementID']], $row['Slug']); $resultingRows[] = $row; //++$c; $lastRow = null; // else break, we're done with this table (element) } else { // error_log('stopped at last: '.$lastRow['Slug'].' '.$lastRow['ActiveDate']); // error_log('stopped at: '.$row['Slug'].' '.$row['ActiveDate']); $batchOffset = -1; $lastRow = null; break 2; } } } // $this->Benchmark->end('pushrows'); if (!$moreThan1Table && $offset == 0 || count($rows) < $batchLimit + 1) { $batchOffset = -1; } else { $batchOffset = $batchOffset + $batchLimit; } } $rows = null; if ($qcount > 1 && $offset > 0 && $moreThan1Table && !empty($resultingRows)) { // error_log('resorting'); // error_log('needed total: '.($limit+$offset)); $resultingRows = $this->NodesHelper->sortNodes($resultingRows, $orderObjects); $resultingRows = $this->NodesHelper->sliceNodes($resultingRows, $limit + $offset, 0); // $resultingRows = array_slice($resultingRows, 0, ($limit+$offset)); $c = count($resultingRows); } ++$qcount; $qArgs = null; } $queries = null; if (!empty($resultingRows)) { if ($offset == 0 && $moreThan1Table) { $resultingRows = $this->NodesHelper->sortNodes($resultingRows, $orderObjects); } $resultingRows = $this->NodesHelper->sliceNodes($resultingRows, $limit, $offset, $moreThan1Table && $offset > 0); } } if (!empty($resultingRows)) { $resultingNodeRefs = ArrayUtils::arrayMultiColumn($resultingRows, 'NodeRef'); if ($nodeQuery->hasParameter('NodeRefs.only') && StringUtils::strToBool($nodeQuery->getParameter('NodeRefs.only')) == true) { $nodeQuery->setResults($resultingNodeRefs); } else { $results = $this->NodeMultiGetDAO->multiGet($resultingNodeRefs, $nodePartials, $forceReadWrite, $checkPermissions, true); $keys = array_map('strval', $resultingNodeRefs); $results = ArrayUtils::arraySortUsingKeys($results, $keys); $keys = null; $resultingNodeRefs = null; $nodeQuery->setResults($results); } } if ($doCounts) { $nodeQuery->setTotalRecords($totalCount); } $this->Benchmark->end('findall'); return $nodeQuery; }