mirror of
https://we.phorge.it/source/phorge.git
synced 2025-02-21 19:19:12 +01:00
Summary: Ref T8441. Ref T7715. Automatically generate a modern "Order" control in ApplicationSearch for engines which fully support SearchField. Notably, this allows the standard "Order" control to automatically support custom field orders. We do this in Maniphest today, but in an ad-hoc way. Test Plan: Performed order-by queries in Almanac (Services), Pholio, Files, People, Projects, and Paste. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7715, T8441 Differential Revision: https://secure.phabricator.com/D13193
336 lines
8.8 KiB
PHP
336 lines
8.8 KiB
PHP
<?php
|
|
|
|
final class PhabricatorFileQuery
|
|
extends PhabricatorCursorPagedPolicyAwareQuery {
|
|
|
|
private $ids;
|
|
private $phids;
|
|
private $authorPHIDs;
|
|
private $explicitUploads;
|
|
private $transforms;
|
|
private $dateCreatedAfter;
|
|
private $dateCreatedBefore;
|
|
private $contentHashes;
|
|
private $minLength;
|
|
private $maxLength;
|
|
private $names;
|
|
private $isPartial;
|
|
|
|
public function withIDs(array $ids) {
|
|
$this->ids = $ids;
|
|
return $this;
|
|
}
|
|
|
|
public function withPHIDs(array $phids) {
|
|
$this->phids = $phids;
|
|
return $this;
|
|
}
|
|
|
|
public function withAuthorPHIDs(array $phids) {
|
|
$this->authorPHIDs = $phids;
|
|
return $this;
|
|
}
|
|
|
|
public function withDateCreatedBefore($date_created_before) {
|
|
$this->dateCreatedBefore = $date_created_before;
|
|
return $this;
|
|
}
|
|
|
|
public function withDateCreatedAfter($date_created_after) {
|
|
$this->dateCreatedAfter = $date_created_after;
|
|
return $this;
|
|
}
|
|
|
|
public function withContentHashes(array $content_hashes) {
|
|
$this->contentHashes = $content_hashes;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Select files which are transformations of some other file. For example,
|
|
* you can use this query to find previously generated thumbnails of an image
|
|
* file.
|
|
*
|
|
* As a parameter, provide a list of transformation specifications. Each
|
|
* specification is a dictionary with the keys `originalPHID` and `transform`.
|
|
* The `originalPHID` is the PHID of the original file (the file which was
|
|
* transformed) and the `transform` is the name of the transform to query
|
|
* for. If you pass `true` as the `transform`, all transformations of the
|
|
* file will be selected.
|
|
*
|
|
* For example:
|
|
*
|
|
* array(
|
|
* array(
|
|
* 'originalPHID' => 'PHID-FILE-aaaa',
|
|
* 'transform' => 'sepia',
|
|
* ),
|
|
* array(
|
|
* 'originalPHID' => 'PHID-FILE-bbbb',
|
|
* 'transform' => true,
|
|
* ),
|
|
* )
|
|
*
|
|
* This selects the `"sepia"` transformation of the file with PHID
|
|
* `PHID-FILE-aaaa` and all transformations of the file with PHID
|
|
* `PHID-FILE-bbbb`.
|
|
*
|
|
* @param list<dict> List of transform specifications, described above.
|
|
* @return this
|
|
*/
|
|
public function withTransforms(array $specs) {
|
|
foreach ($specs as $spec) {
|
|
if (!is_array($spec) ||
|
|
empty($spec['originalPHID']) ||
|
|
empty($spec['transform'])) {
|
|
throw new Exception(
|
|
pht(
|
|
"Transform specification must be a dictionary with keys ".
|
|
"'%s' and '%s'!",
|
|
'originalPHID',
|
|
'transform'));
|
|
}
|
|
}
|
|
|
|
$this->transforms = $specs;
|
|
return $this;
|
|
}
|
|
|
|
public function withLengthBetween($min, $max) {
|
|
$this->minLength = $min;
|
|
$this->maxLength = $max;
|
|
return $this;
|
|
}
|
|
|
|
public function withNames(array $names) {
|
|
$this->names = $names;
|
|
return $this;
|
|
}
|
|
|
|
public function withIsPartial($partial) {
|
|
$this->isPartial = $partial;
|
|
return $this;
|
|
}
|
|
|
|
public function showOnlyExplicitUploads($explicit_uploads) {
|
|
$this->explicitUploads = $explicit_uploads;
|
|
return $this;
|
|
}
|
|
|
|
public function newResultObject() {
|
|
return new PhabricatorFile();
|
|
}
|
|
|
|
protected function loadPage() {
|
|
$files = $this->loadStandardPage(new PhabricatorFile());
|
|
|
|
if (!$files) {
|
|
return $files;
|
|
}
|
|
|
|
// We need to load attached objects to perform policy checks for files.
|
|
// First, load the edges.
|
|
|
|
$edge_type = PhabricatorFileHasObjectEdgeType::EDGECONST;
|
|
$file_phids = mpull($files, 'getPHID');
|
|
$edges = id(new PhabricatorEdgeQuery())
|
|
->withSourcePHIDs($file_phids)
|
|
->withEdgeTypes(array($edge_type))
|
|
->execute();
|
|
|
|
$object_phids = array();
|
|
foreach ($files as $file) {
|
|
$phids = array_keys($edges[$file->getPHID()][$edge_type]);
|
|
$file->attachObjectPHIDs($phids);
|
|
foreach ($phids as $phid) {
|
|
$object_phids[$phid] = true;
|
|
}
|
|
}
|
|
|
|
// If this file is a transform of another file, load that file too. If you
|
|
// can see the original file, you can see the thumbnail.
|
|
|
|
// TODO: It might be nice to put this directly on PhabricatorFile and remove
|
|
// the PhabricatorTransformedFile table, which would be a little simpler.
|
|
|
|
$xforms = id(new PhabricatorTransformedFile())->loadAllWhere(
|
|
'transformedPHID IN (%Ls)',
|
|
$file_phids);
|
|
$xform_phids = mpull($xforms, 'getOriginalPHID', 'getTransformedPHID');
|
|
foreach ($xform_phids as $derived_phid => $original_phid) {
|
|
$object_phids[$original_phid] = true;
|
|
}
|
|
|
|
$object_phids = array_keys($object_phids);
|
|
|
|
// Now, load the objects.
|
|
|
|
$objects = array();
|
|
if ($object_phids) {
|
|
// NOTE: We're explicitly turning policy exceptions off, since the rule
|
|
// here is "you can see the file if you can see ANY associated object".
|
|
// Without this explicit flag, we'll incorrectly throw unless you can
|
|
// see ALL associated objects.
|
|
|
|
$objects = id(new PhabricatorObjectQuery())
|
|
->setParentQuery($this)
|
|
->setViewer($this->getViewer())
|
|
->withPHIDs($object_phids)
|
|
->setRaisePolicyExceptions(false)
|
|
->execute();
|
|
$objects = mpull($objects, null, 'getPHID');
|
|
}
|
|
|
|
foreach ($files as $file) {
|
|
$file_objects = array_select_keys($objects, $file->getObjectPHIDs());
|
|
$file->attachObjects($file_objects);
|
|
}
|
|
|
|
foreach ($files as $key => $file) {
|
|
$original_phid = idx($xform_phids, $file->getPHID());
|
|
if ($original_phid == PhabricatorPHIDConstants::PHID_VOID) {
|
|
// This is a special case for builtin files, which are handled
|
|
// oddly.
|
|
$original = null;
|
|
} else if ($original_phid) {
|
|
$original = idx($objects, $original_phid);
|
|
if (!$original) {
|
|
// If the viewer can't see the original file, also prevent them from
|
|
// seeing the transformed file.
|
|
$this->didRejectResult($file);
|
|
unset($files[$key]);
|
|
continue;
|
|
}
|
|
} else {
|
|
$original = null;
|
|
}
|
|
$file->attachOriginalFile($original);
|
|
}
|
|
|
|
return $files;
|
|
}
|
|
|
|
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
|
|
$joins = parent::buildJoinClauseParts($conn);
|
|
|
|
if ($this->transforms) {
|
|
$joins[] = qsprintf(
|
|
$conn,
|
|
'JOIN %T t ON t.transformedPHID = f.phid',
|
|
id(new PhabricatorTransformedFile())->getTableName());
|
|
}
|
|
|
|
return $joins;
|
|
}
|
|
|
|
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
|
$where = parent::buildWhereClauseParts($conn);
|
|
|
|
if ($this->ids !== null) {
|
|
$where[] = qsprintf(
|
|
$conn,
|
|
'f.id IN (%Ld)',
|
|
$this->ids);
|
|
}
|
|
|
|
if ($this->phids !== null) {
|
|
$where[] = qsprintf(
|
|
$conn,
|
|
'f.phid IN (%Ls)',
|
|
$this->phids);
|
|
}
|
|
|
|
if ($this->authorPHIDs !== null) {
|
|
$where[] = qsprintf(
|
|
$conn,
|
|
'f.authorPHID IN (%Ls)',
|
|
$this->authorPHIDs);
|
|
}
|
|
|
|
if ($this->explicitUploads !== null) {
|
|
$where[] = qsprintf(
|
|
$conn,
|
|
'f.isExplicitUpload = %d',
|
|
(int)$this->explicitUploads);
|
|
}
|
|
|
|
if ($this->transforms !== null) {
|
|
$clauses = array();
|
|
foreach ($this->transforms as $transform) {
|
|
if ($transform['transform'] === true) {
|
|
$clauses[] = qsprintf(
|
|
$conn,
|
|
'(t.originalPHID = %s)',
|
|
$transform['originalPHID']);
|
|
} else {
|
|
$clauses[] = qsprintf(
|
|
$conn,
|
|
'(t.originalPHID = %s AND t.transform = %s)',
|
|
$transform['originalPHID'],
|
|
$transform['transform']);
|
|
}
|
|
}
|
|
$where[] = qsprintf($conn, '(%Q)', implode(') OR (', $clauses));
|
|
}
|
|
|
|
if ($this->dateCreatedAfter !== null) {
|
|
$where[] = qsprintf(
|
|
$conn,
|
|
'f.dateCreated >= %d',
|
|
$this->dateCreatedAfter);
|
|
}
|
|
|
|
if ($this->dateCreatedBefore !== null) {
|
|
$where[] = qsprintf(
|
|
$conn,
|
|
'f.dateCreated <= %d',
|
|
$this->dateCreatedBefore);
|
|
}
|
|
|
|
if ($this->contentHashes !== null) {
|
|
$where[] = qsprintf(
|
|
$conn,
|
|
'f.contentHash IN (%Ls)',
|
|
$this->contentHashes);
|
|
}
|
|
|
|
if ($this->minLength !== null) {
|
|
$where[] = qsprintf(
|
|
$conn,
|
|
'byteSize >= %d',
|
|
$this->minLength);
|
|
}
|
|
|
|
if ($this->maxLength !== null) {
|
|
$where[] = qsprintf(
|
|
$conn,
|
|
'byteSize <= %d',
|
|
$this->maxLength);
|
|
}
|
|
|
|
if ($this->names !== null) {
|
|
$where[] = qsprintf(
|
|
$conn,
|
|
'name in (%Ls)',
|
|
$this->names);
|
|
}
|
|
|
|
if ($this->isPartial !== null) {
|
|
$where[] = qsprintf(
|
|
$conn,
|
|
'isPartial = %d',
|
|
(int)$this->isPartial);
|
|
}
|
|
|
|
return $where;
|
|
}
|
|
|
|
protected function getPrimaryTableAlias() {
|
|
return 'f';
|
|
}
|
|
|
|
public function getQueryApplicationClass() {
|
|
return 'PhabricatorFilesApplication';
|
|
}
|
|
|
|
}
|