Changeset View
Standalone View
src/applications/phriction/query/PhrictionDocumentQuery.php
Show All 11 Lines | final class PhrictionDocumentQuery | ||||
private $needContent; | private $needContent; | ||||
private $status = 'status-any'; | private $status = 'status-any'; | ||||
const STATUS_ANY = 'status-any'; | const STATUS_ANY = 'status-any'; | ||||
const STATUS_OPEN = 'status-open'; | const STATUS_OPEN = 'status-open'; | ||||
const STATUS_NONSTUB = 'status-nonstub'; | const STATUS_NONSTUB = 'status-nonstub'; | ||||
const ORDER_CREATED = 'order-created'; | |||||
const ORDER_UPDATED = 'order-updated'; | |||||
const ORDER_HIERARCHY = 'order-hierarchy'; | const ORDER_HIERARCHY = 'order-hierarchy'; | ||||
public function withIDs(array $ids) { | public function withIDs(array $ids) { | ||||
$this->ids = $ids; | $this->ids = $ids; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function withPHIDs(array $phids) { | public function withPHIDs(array $phids) { | ||||
Show All 26 Lines | public function withStatus($status) { | ||||
return $this; | return $this; | ||||
} | } | ||||
public function needContent($need_content) { | public function needContent($need_content) { | ||||
$this->needContent = $need_content; | $this->needContent = $need_content; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function setOrder($order) { | protected function loadPage() { | ||||
switch ($order) { | return $this->loadStandardPage($this->newResultObject()); | ||||
case self::ORDER_CREATED: | |||||
$this->setOrderVector(array('id')); | |||||
break; | |||||
case self::ORDER_UPDATED: | |||||
$this->setOrderVector(array('updated')); | |||||
break; | |||||
case self::ORDER_HIERARCHY: | |||||
$this->setOrderVector(array('depth', 'title', 'updated')); | |||||
break; | |||||
default: | |||||
throw new Exception(pht('Unknown order "%s".', $order)); | |||||
} | } | ||||
return $this; | public function newResultObject() { | ||||
return new PhrictionDocument(); | |||||
} | } | ||||
protected function loadPage() { | protected function willFilterPage(array $documents) { | ||||
$table = new PhrictionDocument(); | |||||
$conn_r = $table->establishConnection('r'); | |||||
$rows = queryfx_all( | |||||
$conn_r, | |||||
'SELECT d.* FROM %T d %Q %Q %Q %Q', | |||||
$table->getTableName(), | |||||
$this->buildJoinClause($conn_r), | |||||
$this->buildWhereClause($conn_r), | |||||
$this->buildOrderClause($conn_r), | |||||
$this->buildLimitClause($conn_r)); | |||||
$documents = $table->loadAllFromArray($rows); | |||||
if ($documents) { | if ($documents) { | ||||
$ancestor_slugs = array(); | $ancestor_slugs = array(); | ||||
foreach ($documents as $key => $document) { | foreach ($documents as $key => $document) { | ||||
$document_slug = $document->getSlug(); | $document_slug = $document->getSlug(); | ||||
foreach (PhabricatorSlug::getAncestry($document_slug) as $ancestor) { | foreach (PhabricatorSlug::getAncestry($document_slug) as $ancestor) { | ||||
$ancestor_slugs[$ancestor][] = $key; | $ancestor_slugs[$ancestor][] = $key; | ||||
} | } | ||||
} | } | ||||
if ($ancestor_slugs) { | if ($ancestor_slugs) { | ||||
$table = new PhrictionDocument(); | |||||
$conn_r = $table->establishConnection('r'); | |||||
$ancestors = queryfx_all( | $ancestors = queryfx_all( | ||||
$conn_r, | $conn_r, | ||||
'SELECT * FROM %T WHERE slug IN (%Ls)', | 'SELECT * FROM %T WHERE slug IN (%Ls)', | ||||
$document->getTableName(), | $document->getTableName(), | ||||
array_keys($ancestor_slugs)); | array_keys($ancestor_slugs)); | ||||
$ancestors = $table->loadAllFromArray($ancestors); | $ancestors = $table->loadAllFromArray($ancestors); | ||||
$ancestors = mpull($ancestors, null, 'getSlug'); | $ancestors = mpull($ancestors, null, 'getSlug'); | ||||
foreach ($ancestor_slugs as $ancestor_slug => $document_keys) { | foreach ($ancestor_slugs as $ancestor_slug => $document_keys) { | ||||
$ancestor = idx($ancestors, $ancestor_slug); | $ancestor = idx($ancestors, $ancestor_slug); | ||||
foreach ($document_keys as $document_key) { | foreach ($document_keys as $document_key) { | ||||
$documents[$document_key]->attachAncestor( | $documents[$document_key]->attachAncestor( | ||||
$ancestor_slug, | $ancestor_slug, | ||||
$ancestor); | $ancestor); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
epriestley: Just copy this whole block into `willFilterPage()` at the top.
`willFilterPage()` does the… | |||||
Not Done Inline ActionsHow do I get a db connection running? chad: How do I get a db connection running? | |||||
Not Done Inline ActionsIt's fine to just do this: $table = new PhrictionDocument(); $conn = $table->establishConnection('r'); That will properly pool/reuse the connection internally. epriestley: It's fine to just do this:
```
$table = new PhrictionDocument();
$conn = $table… | |||||
} | } | ||||
Not Done Inline ActionsAlthough this code should move to willFilterPage(), I think we still need it. Without this code, I would expect policies to fail to apply correctly to child pages. For example:
epriestley: Although this code should move to `willFilterPage()`, I think we still need it.
Without this… | |||||
Not Done Inline ActionsYeah, I don't know what the fix is here after fiddling with it for a while. It looked like we filter pages already with willFilterPage, How do I integrate this check in with it? chad: Yeah, I don't know what the fix is here after fiddling with it for a while. It looked like we… | |||||
return $documents; | |||||
} | |||||
protected function willFilterPage(array $documents) { | |||||
// To view a Phriction document, you must also be able to view all of the | // To view a Phriction document, you must also be able to view all of the | ||||
// ancestor documents. Filter out documents which have ancestors that are | // ancestor documents. Filter out documents which have ancestors that are | ||||
// not visible. | // not visible. | ||||
$document_map = array(); | $document_map = array(); | ||||
foreach ($documents as $document) { | foreach ($documents as $document) { | ||||
$document_map[$document->getSlug()] = $document; | $document_map[$document->getSlug()] = $document; | ||||
foreach ($document->getAncestors() as $key => $ancestor) { | foreach ($document->getAncestors() as $key => $ancestor) { | ||||
Show All 25 Lines | foreach ($documents as $document_key => $document) { | ||||
if (!isset($filtered_map[$ancestor_key])) { | if (!isset($filtered_map[$ancestor_key])) { | ||||
$this->didRejectResult($documents[$document_key]); | $this->didRejectResult($documents[$document_key]); | ||||
unset($documents[$document_key]); | unset($documents[$document_key]); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if (!$documents) { | if (!$documents) { | ||||
return $documents; | return $documents; | ||||
} | } | ||||
Not Done Inline Actionscan't say I understand this either. chad: can't say I understand this either. | |||||
Not Done Inline ActionsThe code above uses unset(...) to remove documents from the list, so it's possible we'll reach this point with zero documents left. If the list is empty, this just returns the empty list early before doing more work. If we didn't and needContent was set, we'd issue a bad id IN (<empty>) query on line 176. epriestley: The code above uses `unset(...)` to remove documents from the list, so it's possible we'll… | |||||
if ($this->needContent) { | if ($this->needContent) { | ||||
$contents = id(new PhrictionContent())->loadAllWhere( | $contents = id(new PhrictionContent())->loadAllWhere( | ||||
'id IN (%Ld)', | 'id IN (%Ld)', | ||||
mpull($documents, 'getContentID')); | mpull($documents, 'getContentID')); | ||||
foreach ($documents as $key => $document) { | foreach ($documents as $key => $document) { | ||||
$content_id = $document->getContentID(); | $content_id = $document->getContentID(); | ||||
if (empty($contents[$content_id])) { | if (empty($contents[$content_id])) { | ||||
unset($documents[$key]); | unset($documents[$key]); | ||||
continue; | continue; | ||||
} | } | ||||
$document->attachContent($contents[$content_id]); | $document->attachContent($contents[$content_id]); | ||||
} | } | ||||
} | } | ||||
return $documents; | return $documents; | ||||
} | } | ||||
protected function buildJoinClause(AphrontDatabaseConnection $conn) { | protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { | ||||
$join = ''; | $joins = array(); | ||||
if ($this->getOrderVector()->containsKey('updated')) { | if ($this->getOrderVector()->containsKey('updated')) { | ||||
$content_dao = new PhrictionContent(); | $content_dao = new PhrictionContent(); | ||||
$join = qsprintf( | $joins[] = qsprintf( | ||||
$conn, | $conn, | ||||
'JOIN %T c ON d.contentID = c.id', | 'JOIN %T c ON d.contentID = c.id', | ||||
$content_dao->getTableName()); | $content_dao->getTableName()); | ||||
} | } | ||||
return $join; | return $joins; | ||||
} | } | ||||
protected function buildWhereClause(AphrontDatabaseConnection $conn) { | |||||
$where = array(); | |||||
if ($this->ids) { | protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { | ||||
$where = parent::buildWhereClauseParts($conn); | |||||
if ($this->ids !== null) { | |||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn, | $conn, | ||||
'd.id IN (%Ld)', | 'd.id IN (%Ld)', | ||||
$this->ids); | $this->ids); | ||||
} | } | ||||
if ($this->phids) { | if ($this->phids !== null) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn, | $conn, | ||||
'd.phid IN (%Ls)', | 'd.phid IN (%Ls)', | ||||
$this->phids); | $this->phids); | ||||
} | } | ||||
if ($this->slugs) { | if ($this->slugs !== null) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn, | $conn, | ||||
'd.slug IN (%Ls)', | 'd.slug IN (%Ls)', | ||||
$this->slugs); | $this->slugs); | ||||
} | } | ||||
if ($this->statuses) { | if ($this->statuses !== null) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn, | $conn, | ||||
'd.status IN (%Ld)', | 'd.status IN (%Ld)', | ||||
$this->statuses); | $this->statuses); | ||||
} | } | ||||
if ($this->slugPrefix) { | if ($this->slugPrefix !== null) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn, | $conn, | ||||
'd.slug LIKE %>', | 'd.slug LIKE %>', | ||||
$this->slugPrefix); | $this->slugPrefix); | ||||
} | } | ||||
if ($this->depths) { | if ($this->depths !== null) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn, | $conn, | ||||
'd.depth IN (%Ld)', | 'd.depth IN (%Ld)', | ||||
$this->depths); | $this->depths); | ||||
} | } | ||||
switch ($this->status) { | switch ($this->status) { | ||||
case self::STATUS_OPEN: | case self::STATUS_OPEN: | ||||
Show All 16 Lines | switch ($this->status) { | ||||
)); | )); | ||||
break; | break; | ||||
case self::STATUS_ANY: | case self::STATUS_ANY: | ||||
break; | break; | ||||
default: | default: | ||||
throw new Exception(pht("Unknown status '%s'!", $this->status)); | throw new Exception(pht("Unknown status '%s'!", $this->status)); | ||||
} | } | ||||
$where[] = $this->buildPagingClause($conn); | return $where; | ||||
} | |||||
return $this->formatWhereClause($where); | public function getBuiltinOrders() { | ||||
return array( | |||||
self::ORDER_HIERARCHY => array( | |||||
'vector' => array('depth', 'title', 'updated'), | |||||
'name' => pht('Hierarchy'), | |||||
), | |||||
) + parent::getBuiltinOrders(); | |||||
} | } | ||||
public function getOrderableColumns() { | public function getOrderableColumns() { | ||||
return parent::getOrderableColumns() + array( | return parent::getOrderableColumns() + array( | ||||
'depth' => array( | 'depth' => array( | ||||
'table' => 'd', | 'table' => 'd', | ||||
'column' => 'depth', | 'column' => 'depth', | ||||
'reverse' => true, | 'reverse' => true, | ||||
Show All 38 Lines | protected function willExecuteCursorQuery( | ||||
PhabricatorCursorPagedPolicyAwareQuery $query) { | PhabricatorCursorPagedPolicyAwareQuery $query) { | ||||
$vector = $this->getOrderVector(); | $vector = $this->getOrderVector(); | ||||
if ($vector->containsKey('title')) { | if ($vector->containsKey('title')) { | ||||
$query->needContent(true); | $query->needContent(true); | ||||
} | } | ||||
} | } | ||||
protected function getPrimaryTableAlias() { | |||||
return 'd'; | |||||
} | |||||
public function getQueryApplicationClass() { | public function getQueryApplicationClass() { | ||||
return 'PhabricatorPhrictionApplication'; | return 'PhabricatorPhrictionApplication'; | ||||
} | } | ||||
} | } |
Just copy this whole block into willFilterPage() at the top.
willFilterPage() does the check, but it depends on page ancestry data which is provided by this block. If we don't run this code, we never set the correct ancestors on the documents, so when we go to run the check it just looks like every document has no ancestors and doesn't need any special rules/policies.